The first inquiry is to understand where Figure-8-Knots occur. Specifically, the locational distribution of Figure-8-Knots on khipu cords, groups, and khipus.
As mentioned in the Figure-8-Knot Study Introduction, it is important to distinguish between Figure-8-Knots in Lockean vs Non-Lockean knot sequences on a cord. When a Figure-8-knot is the last knot of more than one knot on a cord (i.e. cord values > 9), it is likely to be a Lockean knot sequence. However, when a Figure-8-knot is the only knot of a cord, it may have both numerical and/or semiotic values. The former (Lockean) knot is called a Trailing Figure-8-Knot, and the latter a Sole Figure-8-Knot.
The investigation proceeds from the bottom to the top. We investigate locational distributions of Figure-8-Knots on khipu cords, groups, and finally khipus.
These are the questions we will answer:
How often does 1 occur, compared to other digits?
What is the locational distributions of Figure-8-Knots on khipu cords?
What are common cord knot-sequences and what “names” do we give them?
What is the locational distributions of Figure-8-Knots along cord groups?
What is the locational distributions of Figure-8-Knots along khipus?
1. Is 1 Special? How often does it occur, and how often does it occur as the last digit?
An analysis of how often the number 1 occurs in the khipu corpus reveals the difference between Lockean and Non-Lockean Figure-8-Knots.
1.1 Sample Study: City Populations
First, let’s look at how 1 distributes in a typical “organic” data se. In a sense, this is a “Lockean” approach, reading the rightmost digit of a number. As a sample study, we can get 10,000 random cities from the US Census, and look at the distribution of the last, rightmost digit:
Code
```{python}K_VERBOSE_MODE = Falseimport osimport numpy as npfrom scipy import statsimport pandas as pdfrom pandas import Series, DataFramefrom collections import Counterimport qollqa_chuspa as qc # A Khipu Maker is known (in Quechua) as a Khipu Kamayuqimport utils_loom as uloomimport utils_khipu as ukhipuimport utils_pandas as upandaimport utils_kfg_locations as uloc# Plotlyimport plotlyfrom plotly.offline import iplot, init_notebook_modeimport plotly.graph_objects as goimport plotly.express as pximport plotly.figure_factory as ffplotly.offline.init_notebook_mode(connected = False)# Load all khipus(khipu_dict, all_khipus) = qc.fetch_khipus()KFG_Names = ukhipu.kfg_order(khipu_dict.keys()) if (K_VERBOSE_MODE): print(f"Loaded {len(all_khipus)} khipus")```
Code
```{python}census_cities = pd.read_csv(f"./data/CSV/us_cities_random_10000.csv")census_cities.head()last_digit_counts = Counter([int(str(x)[-1]) for x in census_cities['Population'].to_list()])#Make a plotly bar chart of the last digit counts, show title and labels, and set max width to 1000p x# Use ETBookOT as the font fig = px.bar(x=last_digit_counts.keys(), y=last_digit_counts.values())fig.update_layout(title='Last Digit Counts for 10,000 Random Cities', xaxis_title='Last Digit', yaxis_title='Count',font_family="ETBookOT", font_size=20)fig.update_layout(width=950) fig.show()```
It’s close to a flat/uniform distribution (each digit landing around ~1,000, i.e. ~10%), which is what you’d expect from real population counts — unlike digits earlier in a number (which tend to follow Benford’s Law), the last digit of a large, organically-generated count is essentially uniformly random.
1.2 Khipu Analysis
Let’s look in the Khipu Field Guide? I will make two crucial distinctions: What is the last digit of the value of the cord when it is greater than or equal to 10 (i.e. a Trailing “Lockean” knots)? And what is the last digit of the value of the cord when it is less than 10 (ie. a Sole knot on the cord)? These values are not the same.
Code
```{python}# for all the khipus in the KFG, get the last digit of the value of the cord # Skip cords with value 0 (no knots)pendant_cords = uloom.flatten_list([aKhipu.all_cords(include_bottom_cords=True, include_top_cords=True, include_subsidiaries=False) for aKhipu in all_khipus])subsidiary_cords = uloom.flatten_list([aKhipu.all_cords(include_bottom_cords=False, include_top_cords=False, include_subsidiaries=True) for aKhipu in all_khipus])all_cords = uloom.flatten_list([aKhipu.all_cords(include_bottom_cords=True, include_top_cords=True, include_subsidiaries=True) for aKhipu in all_khipus])header_labels = ["Last Digit"] + [f"{i}" for i in range(10)]row_labels = ["Digit Count"]def make_markdown_table(header_labels, row_labels, row_values): tablestr = "| " + " | ".join(header_labels) + " |\n" tablestr += "|" + "|".join([":------:" for _ in header_labels]) + "|\n" for row_label, row_value in zip(row_labels, row_values): row_count = sum(row_value) tablestr += f"| {row_label} | " + " | ".join([f"{uloom.percent_info(count, row_count, as_html=True)}" for count in row_value]) + " |\n" return tablestrimport sysk_MAX_KNOTTED_VALUE = sys.maxsize # 9223372036854775807 Largest possible int value for a corddef make_last_digit_counts(a_min_knotted_value=0, a_max_knotted_value=k_MAX_KNOTTED_VALUE, print_table=False): pendant_cord_last_digits = [int(str(aCord.knotted_value)[-1]) for aCord in pendant_cords if (aCord.knotted_value >= a_min_knotted_value) and (aCord.knotted_value <= a_max_knotted_value)] subsidiary_cord_last_digits = [int(str(aCord.knotted_value)[-1]) for aCord in subsidiary_cords if (aCord.knotted_value >= a_min_knotted_value) and (aCord.knotted_value <= a_max_knotted_value)] all_cord_last_digits = [int(str(aCord.knotted_value)[-1]) for aCord in all_cords if (aCord.knotted_value >= a_min_knotted_value) and (aCord.knotted_value <= a_max_knotted_value)] pendant_cord_last_digit_counts = Counter(pendant_cord_last_digits) subsidiary_cord_last_digit_counts = Counter(subsidiary_cord_last_digits) all_cord_last_digit_counts = Counter(all_cord_last_digits) #Make a plotly bar chart of the last digit counts, show title and labels, and set max width to 1000p x fig = px.bar(x=pendant_cord_last_digit_counts.keys(), y=pendant_cord_last_digit_counts.values()) the_title = f"Last Digit Counts for Pendant Cords ≥ {a_min_knotted_value}" if (a_max_knotted_value < k_MAX_KNOTTED_VALUE): the_title += f" and ≤ {a_max_knotted_value}" fig.update_layout(title=the_title, xaxis_title='Last Digit', yaxis_title='Count',font_family="ETBookOT", font_size=20) fig.update_layout(width=945) fig.show() #Make a plotly bar chart of the last digit counts, show title and labels, and set max width to 1000p x fig = px.bar(x=subsidiary_cord_last_digit_counts.keys(), y=subsidiary_cord_last_digit_counts.values()) the_title = f"Last Digit Counts for Subsidiary Cords ≥ {a_min_knotted_value}" if (a_max_knotted_value < k_MAX_KNOTTED_VALUE): the_title += f" and ≤ {a_max_knotted_value}" fig.update_layout(title=the_title, xaxis_title='Last Digit', yaxis_title='Count',font_family="ETBookOT", font_size=20) fig.update_layout(width=945) fig.show() if print_table: print(f"Pendant Cords Last Digit Counts >= {a_min_knotted_value}") row_values = [list(pendant_cord_last_digit_counts.values())] print(make_markdown_table(header_labels, row_labels, row_values)) print(f"Subsidiary Cords Last Digit Counts >= {a_min_knotted_value}") row_values = [list(subsidiary_cord_last_digit_counts.values())] print(make_markdown_table(header_labels, row_labels, row_values))make_last_digit_counts(a_min_knotted_value=0, a_max_knotted_value=k_MAX_KNOTTED_VALUE, print_table=False)make_last_digit_counts(a_min_knotted_value=10, a_max_knotted_value=k_MAX_KNOTTED_VALUE, print_table=False)make_last_digit_counts(a_min_knotted_value=1, a_max_knotted_value=9, print_table=False)```
The graphs show that Trailing (Lockean-style)digits follow a typical uniform distribution, while Sole digits follow a non-uniform decreasing distribution, heavily centered on 1.
1.3 How often does 1 occur as a Trailing vs Sole Digit on Pendant vs Subsidiary Cords?
Let’s look at the distribution of 1’s as the last digit vs a sole digit on pendant cords vs subsidiary cords.
Code
```{python}pendant_cord_trailing_ones = [aCord for aCord in pendant_cords if (aCord.knotted_value > 1) and (int(str(aCord.knotted_value)[-1]) == 1)]pendant_cord_sole_ones = [aCord for aCord in pendant_cords if (aCord.knotted_value == 1)]subsidiary_cord_trailing_ones = [aCord for aCord in subsidiary_cords if (aCord.knotted_value > 1) and (int(str(aCord.knotted_value)[-1]) == 1)]subsidiary_cord_sole_ones = [aCord for aCord in subsidiary_cords if (aCord.knotted_value == 1)]# Print as a markdown tabletotal_pendant_cord_1s = len(pendant_cord_trailing_ones) + len(pendant_cord_sole_ones)total_subsidiary_cord_1s = len(subsidiary_cord_trailing_ones) + len(subsidiary_cord_sole_ones)do_print = Falseif do_print: print(f"| Cord Type | Last Digit 1's (Trailing 1's) | Sole 1's |") print(f"|:------|------:|-------:|") print(f"| Pendant Cords | {uloom.percent_info(len(pendant_cord_trailing_ones), total_pendant_cord_1s, as_html=True)} | {uloom.percent_info(len(pendant_cord_sole_ones), total_pendant_cord_1s, as_html=True)} |") print(f"| Subsidiary Cords | {uloom.percent_info(len(subsidiary_cord_trailing_ones), total_subsidiary_cord_1s, as_html=True)} | {uloom.percent_info(len(subsidiary_cord_sole_ones), total_subsidiary_cord_1s, as_html=True)} |")```
Cord Type
Last Digit 1’s (Trailing 1’s)
Sole 1’s
Pendant Cords
33% (1641 of 4996)
67% (3355 of 4996)
Subsidiary Cords
12% (412 of 3424)
88% (3012 of 3424)
2. Common Knot Sequences
With what other knot types do figure-8-knots associate?
Let’s look at all cords in the KFG database, and gather the most common knot type sequences, in conventional Lockean order (from the primary cord down):
2.1 Common Knot Sequences for All Khipu Cords
Code
```{python}import osimport numpy as npfrom scipy import statsimport pandas as pdfrom pandas import Series, DataFramefrom collections import Counterimport qollqa_chuspa as qc # A Khipu Maker is known (in Quechua) as a Khipu Kamayuqimport utils_loom as uloomimport utils_khipu as ukhipuimport utils_pandas as upandaimport utils_kfg_locations as uloc# Plotlyimport plotlyfrom plotly.offline import iplot, init_notebook_modeimport plotly.graph_objects as goimport plotly.express as pximport plotly.figure_factory as ffplotly.offline.init_notebook_mode(connected = False)# Load all khipus(khipu_dict, all_khipus) = qc.fetch_khipus()KFG_Names = ukhipu.kfg_order(khipu_dict.keys()) # pyright: ignore[reportArgumentType]khipu_cords_dict = {KFG_Name: khipu_dict[KFG_Name].all_cords(include_bottom_cords=True, include_top_cords=True, include_subsidiaries=True) for KFG_Name in KFG_Names}all_khipu_cords = uloom.flatten_list([khipu_cords_dict[KFG_Name] for KFG_Name in KFG_Names])total_num_khipu_cords = len(all_khipu_cords)```
Code
```{python}kMAXMOSTCOMMON = 200def knot_sequence(aCord): cord_knots = aCord.all_knots(include_subsidiaries=False) knot_types = [knot.knot_type for knot in cord_knots] sequence_string = ','.join(knot_types) return sequence_string if sequence_string != '' else 'No Knots'def most_common_knot_sequences(cords): all_knot_sequences = [knot_sequence(aCord) for aCord in cords] knot_sequence_counts = Counter(all_knot_sequences) return [(the_knot_sequence, count) for the_knot_sequence, count in knot_sequence_counts.most_common() if count > kMAXMOSTCOMMON]def knot_sequence_table(cords=None, print_common_sequences = False): if cords is None: cords = all_khipu_cords the_most_common_knot_sequences = most_common_knot_sequences(cords) the_most_common_pendant_knot_sequences = most_common_knot_sequences([aCord for aCord in cords if aCord.is_pendant_cord()]) the_most_common_pendant_knot_sequences_dict = {the_knot_sequence:count for (the_knot_sequence, count) in the_most_common_pendant_knot_sequences} if print_common_sequences: # print(f"Most common knot sequences {len(the_most_common_knot_sequences)} with more than {kMAXMOSTCOMMON} occurrences:") for (index, (the_knot_sequence, count)) in enumerate(the_most_common_knot_sequences): if the_knot_sequence.strip() == '': the_knot_sequence = 'No_Knots' the_num_pendant_knot_sequence = the_most_common_pendant_knot_sequences_dict.get(the_knot_sequence, 0) the_num_subsidiary_knot_sequence = count - the_num_pendant_knot_sequence print(f"| {index} | {the_knot_sequence} | {uloom.percent_info(count, len(cords), as_html=True)} | {uloom.percent_info(the_num_pendant_knot_sequence, count, as_html=True)} | {uloom.percent_info(the_num_subsidiary_knot_sequence, count, as_html=True)} | ") return the_most_common_knot_sequencesdo_print = Falseif do_print: knot_sequence_table(print_common_sequences = do_print); pendant_cords = [aCord for aCord in all_khipu_cords if aCord.is_pendant_cord()] subsidiary_cords = [aCord for aCord in all_khipu_cords if aCord.is_subsidiary_cord()] print(f"# All Cords = {len([aCord for aCord in all_khipu_cords])}") print(f"# Pendant Cords = {uloom.percent_info(len(pendant_cords), len(all_khipu_cords), as_html=True)}") print(f"# Subsidiary Cords = {uloom.percent_info(len(subsidiary_cords), len(all_khipu_cords), as_html=True)}")```
The top knot sequences in the KFG database with more than 200 occurrences (out of ~60,000 cords) are:
Index
Knot Type Sequence
# Cords
# Pendant Cords
# Subsidiary Cords
-
-
59144 Cords
72% (42572 of 59144)
28% (16572 of 59144)
0
No Knots
30% (19058 of 62837)
74% (14195 of 19058)
26% (4863 of 19058)
1
L
21% (13000 of 62837)
64% (8317 of 13000)
36% (4683 of 13000)
2
S,L
14% (8509 of 62837)
80% (6801 of 8509)
20% (1708 of 8509)
3
E
10% (6212 of 62837)
52% (3253 of 6212)
48% (2959 of 6212)
4
S
9% (5963 of 62837)
72% (4293 of 5963)
28% (1670 of 5963)
5
S,S,L
4% (2617 of 62837)
89% (2332 of 2617)
11% (285 of 2617)
6
S,E
2% (1325 of 62837)
78% (1033 of 1325)
22% (292 of 1325)
7
S,S
2% (1127 of 62837)
84% (948 of 1127)
16% (179 of 1127)
8
L,L
1% (668 of 62837)
84% (558 of 668)
16% (110 of 668)
9
S,S,S,L
1% (586 of 62837)
94% (548 of 586)
6% (38 of 586)
10
L,L,L
1% (412 of 62837)
92% (380 of 412)
8% (32 of 412)
11
S,S,E
1% (338 of 62837)
89% (301 of 338)
11% (37 of 338)
12
L,E
0% (313 of 62837)
67% (211 of 313)
33% (102 of 313)
13
S,S,S
0% (250 of 62837)
82% (206 of 250)
18% (44 of 250)
14
S,L,E
0% (207 of 62837)
0% (0 of 207)
100% (207 of 207)
You might have expected the conventional Lockean set of Single, Single, Long knots as the typical sequence ie. S,S,L… Actually the most common is no knots. However this simple count reveals:
The most common value range is 2-9, represented as an L knot, occupies 21% of the cords.
The next most common value range 11-99, represented as S,L, occupies 14% of the cords.
Finally, the value 1, represented as a Sole Figure-8-Knot E, occupies 10% of the cords.
ESole Eight Knots reside equally (52%/48%) on pendants vs subsidiaries. This is unlike any of the other knot sequences which are typically 3 to 1 or 4 to 1 ratios for pendants vs subsidiaries
S,E, S,S,E, L,E all have a Trailing Figure-Eight-Knot.
The value range 101-999, represented as S,S,L knot sequences are fifth on the list.
As we will see, Figure-8-Knots are present on 15% of all khipu cords, an unexpectedly high frequency in the knot sequences and a wholly unexpected result. They usually appear in the form of Sole Eight-Knots (i.e. E), and Trailing Eight-Knots (i.e. S,E and S,S,E, S,L,E).
2.2 Lockean vs Non-Lockean Knot Sequences:
How do Lockean vs Non-Lockean knot sequences distribute?
Code
```{python}def knot_sequence(aCord): return ("".join([aKnot.knot_type for aKnot in aCord.all_knots(include_subsidiaries=False)])).strip()def is_lockean_sequence(aKnotSequence): is_lockean = False knot_set = set(aKnotSequence) if (len(aKnotSequence)==0) or (knot_set == {}) or (knot_set == {'S'}): is_lockean = True elif knot_set == {'L'}: is_lockean = len(aKnotSequence) == 1 elif (knot_set == {'S', 'L'}): is_lockean = aKnotSequence[-1] == "L" and (all([theChar == 'S' for theChar in aKnotSequence[:-1]])) return is_lockeanall_cords = uloom.flatten_list([aKhipu.all_cords(include_subsidiaries=True, include_top_cords=True) for aKhipu in all_khipus]) lockean_cords = [aCord for aCord in all_cords if is_lockean_sequence(knot_sequence(aCord))]non_lockean_cords = [aCord for aCord in all_cords if not is_lockean_sequence(knot_sequence(aCord))]print(f"{uloom.percent_info(len(lockean_cords), len(all_cords))} of all cords have Lockean knot-sequences\n")print("20 Most Common Lockean Knot Sequences")print(Counter([knot_sequence(aCord) for aCord in lockean_cords]).most_common(20))print("20 Most Common Non Lockean Knot Sequences")print(Counter([knot_sequence(aCord) for aCord in non_lockean_cords]).most_common(20))lockean_counts = Counter([knot_sequence(aCord) for aCord in lockean_cords]).most_common(25)[::-1]non_lockean_counts = Counter([knot_sequence(aCord) for aCord in non_lockean_cords]).most_common(25)[::-1]fig = (go.Figure(go.Bar( x=[item[1] for item in lockean_counts], y=[item[0] for item in lockean_counts], orientation='h', )))fig.layout.update(width=950, height=400, title_text='Top 15 Lockean Knot Sequence Frequency')fig.update_layout( font_family="Lucida Grande", font_color="black", title_font_family="Lucida Grande", title_font_color="black", legend_title_font_color="black")fig.show()fig = (go.Figure(go.Bar( x=[item[1] for item in non_lockean_counts], y=[item[0] for item in non_lockean_counts], orientation='h', )))fig.layout.update(width=950, height=600, title_text='Top 25 Non Lockean Knot Sequence Frequency')fig.update_layout( font_family="Lucida Grande", font_color="black", title_font_family="Lucida Grande", title_font_color="black", legend_title_font_color="black")fig.show()```
For cords with a Figure-8-knot, what are the common knot sequences?
Code
```{python}eight_knot_cords = [aCord for aCord in all_cords if (not is_lockean_sequence(knot_sequence(aCord)) and ('E' in knot_sequence(aCord)))]eight_knot_counts = Counter([knot_sequence(aCord) for aCord in eight_knot_cords]).most_common(25)[::-1]fig = (go.Figure(go.Bar( x=[item[1] for item in eight_knot_counts], y=[item[0] for item in eight_knot_counts], orientation='h', )))fig.layout.update(width=950, height=600, title_text='Top 25 Eight Knot Sequence Frequency')fig.update_layout( font_family="Lucida Grande", font_color="black", title_font_family="Lucida Grande", title_font_color="black", legend_title_font_color="black")fig.show()```
3. Figure-Eight-Knot Sequence Types:
3.1 Figure-Eight-Knot Types
We start by building a dataset of Figure-Eight-Knot Cords of six types:
Cords that have a Sole Figure-Eight-Knot where there is only one knot on the cord (ie. no single knots, long knots, etc)
Cords that have Multiple Eight-Knots (where there is more than one knot, and every knot is an eight-knot)
Cords that have a Leading Figure-Eight-Knot, succeeded by other knots
Cords that have a Middle Figure-Eight-Knot, sandwiched between other knots
Cords that have a Trailing Figure-Eight-Knot, preceded by other knots
Cords that have Mixed Eight-Knots (i.e Leading + Middle, or Middle + Trailing or Leading + Middle + Trailing, but not Only Eight-Knots)
Code
```{python}def has_eight_knot(aCord): """ Does at least one eight-knot exist on the cord? """ search_knots = aCord.all_knots(include_subsidiaries=False) eight_knots = [aKnot for aKnot in search_knots if aKnot.is_eight_knot()] return len(eight_knots) > 0def has_sole_eight_knot(aCord): """ Does the cord have only one knot, and that knot is an eight-knot """ search_knots = aCord.all_knots(include_subsidiaries=False) eight_knots = [aKnot for aKnot in search_knots if aKnot.is_eight_knot()] return (len(search_knots) == 1) and (len(eight_knots) == 1)def has_mutiple_only_eight_knots(aCord): """ Does the cord have multiple eight-knots, and only eight-knots? """ search_knots = aCord.all_knots(include_subsidiaries=False) is_only_eight_knots = all([aKnot.is_eight_knot() for aKnot in search_knots]) return (len(search_knots) >= 2) and is_only_eight_knotsdef has_leading_eight_knot(aCord): """ Does the cord have multiple knots, and the first knot is an eight-knot and it's not only eight-knots? """ search_knots = aCord.all_knots(include_subsidiaries=False) return (len(search_knots) >= 2) and (not has_mutiple_only_eight_knots(aCord)) and search_knots[0].is_eight_knot()def has_middle_eight_knot(aCord): """ Does the cord have at least 3 knots, and at least one of the middle knots is an eight-knot and it's not only eight-knots? """ search_knots = aCord.all_knots(include_subsidiaries=False) return (len(search_knots) >= 3) and (not has_mutiple_only_eight_knots(aCord)) and any([aKnot.is_eight_knot() for aKnot in search_knots[1:-1]])def has_trailing_eight_knot(aCord): """ Does the cord have multiple knots, and the last knot is an eight-knot and it's not only eight-knots? """ search_knots = aCord.all_knots(include_subsidiaries=False) return (len(search_knots) >= 2) and (not has_mutiple_only_eight_knots(aCord)) and search_knots[-1].is_eight_knot()def has_mixed_eight_knot(aCord): """ **Mixed Eight-Knots** (i.e Leading + Middle, or Middle + Trailing or Leading + Middle + Trailing, but not Only Eight-Knots) """ is_only_eight_knots = has_mutiple_only_eight_knots(aCord) has_leading_middle_trailing = (has_leading_eight_knot(aCord) and has_middle_eight_knot(aCord) and has_trailing_eight_knot(aCord)) has_leading_middle = (has_leading_eight_knot(aCord) and has_middle_eight_knot(aCord)) has_middle_trailing = (has_middle_eight_knot(aCord) and has_trailing_eight_knot(aCord)) has_leading_trailing = (has_leading_eight_knot(aCord) and has_trailing_eight_knot(aCord)) return (is_only_eight_knots and has_leading_middle_trailing or has_leading_middle or has_middle_trailing or has_leading_trailing) def tag_cord_knots(aCord): """ Tag the knots on a cord with an eight-knot type """ tag = "None" if has_sole_eight_knot(aCord): tag = "Sole_Eight_Knot" elif has_mutiple_only_eight_knots(aCord): tag = "Multiple_Only_Eight_Knots" elif has_mixed_eight_knot(aCord): tag = "Mixed_Eight_Knot" elif has_leading_eight_knot(aCord): tag = "Leading_Eight_Knot" elif has_middle_eight_knot(aCord): tag = "Middle_Eight_Knot" elif has_trailing_eight_knot(aCord): tag = "Trailing_Eight_Knot" return tagdef tagged_kfg_cords(aKFG_Name, khipu_cords): """ Return a list of tagged cords tuples (cord, tag_name) for a given KFG_Name """ tagged_eight_knot_cords = [] for aCord in khipu_cords: tag = tag_cord_knots(aCord) if tag != "None": tagged_eight_knot_cords.append({'KFG_Name': aKFG_Name, 'cord_name': aCord.pendant_name, 'group_pendant_cord_index': aCord.find_pendant_cord().group_index(), 'group_position': aCord.find_pendant_cord().cord_group.position(), 'num_group_pendants': aCord.find_pendant_cord().cord_group.num_pendant_cords(), 'tag': tag, 'is_pendant': aCord.is_pendant_cord()}) return tagged_eight_knot_cordsdef tag_eight_knot_cords(pendant_only=False, subsidiary_only=False): """ Return a dataframe of tagged eight_knot cords """ tagged_eight_knot_cords = [] for aKFG_Name in KFG_Names: khipu_cords = khipu_cords_dict[aKFG_Name] if pendant_only: khipu_cords = [aCord for aCord in khipu_cords if aCord.is_pendant_cord()] if subsidiary_only: khipu_cords = [aCord for aCord in khipu_cords if aCord.is_subsidiary_cord()] tagged_eight_knot_cords += tagged_kfg_cords(aKFG_Name, khipu_cords) return DataFrame(tagged_eight_knot_cords, columns=['KFG_Name', 'cord_name', 'group_pendant_cord_index', 'group_position', 'num_group_pendants', 'tag', 'is_pendant'])eight_knot_cords_df = tag_eight_knot_cords()eight_knot_cords_df.to_csv(f"{uloc.fieldmarks_data_dir()}/eight_knot_cords.csv", index=False)pendant_eight_knot_cords_df = tag_eight_knot_cords(pendant_only=True)subsidiary_eight_knot_cords_df = tag_eight_knot_cords(subsidiary_only=True)eight_knot_cords_df.head()```
KFG_Name
cord_name
group_pendant_cord_index
group_position
num_group_pendants
tag
is_pendant
0
CM009
p1
0
0
5
Sole_Eight_Knot
True
1
CM009
p12
2
2
8
Sole_Eight_Knot
True
2
CM009
p15
5
2
8
Sole_Eight_Knot
True
3
CM009
p17
7
2
8
Sole_Eight_Knot
True
4
CM009
p21
3
3
4
Sole_Eight_Knot
True
Code
```{python}def has_sole_eight_knot(aCord): """ Does the cord have only one knot, and that knot is an eight-knot """ search_knots = aCord.all_knots(include_subsidiaries=False) eight_knots = [aKnot for aKnot in search_knots if aKnot.is_eight_knot()] return (len(search_knots) == 1) and (len(eight_knots) == 1)def has_mutiple_only_eight_knots(aCord): """ Does the cord have multiple eight-knots, and only eight-knots? """ search_knots = aCord.all_knots(include_subsidiaries=False) is_only_eight_knots = all([aKnot.is_eight_knot() for aKnot in search_knots]) return (len(search_knots) >= 2) and is_only_eight_knotsdef has_leading_eight_knot(aCord): """ Does the cord have multiple knots, and the first knot is an eight-knot and it's not only eight-knots? """ search_knots = aCord.all_knots(include_subsidiaries=False) return (len(search_knots) >= 2) and (not has_mutiple_only_eight_knots(aCord)) and search_knots[0].is_eight_knot()def has_middle_eight_knot(aCord): """ Does the cord have at least 3 knots, and at least one of the middle knots is an eight-knot and it's not only eight-knots? """ search_knots = aCord.all_knots(include_subsidiaries=False) return (len(search_knots) >= 3) and (not has_mutiple_only_eight_knots(aCord)) and any([aKnot.is_eight_knot() for aKnot in search_knots[1:-1]])def has_trailing_eight_knot(aCord): """ Does the cord have multiple knots, and the last knot is an eight-knot and it's not only eight-knots? """ search_knots = aCord.all_knots(include_subsidiaries=False) return (len(search_knots) >= 2) and (not has_mutiple_only_eight_knots(aCord)) and search_knots[-1].is_eight_knot()```
Code
```{python}def num_eight_knot_cords(aKFG_Name): """ Number of eight-knot cords in a Khipu """ return eight_knot_cords_df[eight_knot_cords_df['KFG_Name'] == aKFG_Name].shape[0]def num_pendant_eight_knot_cords(aKFG_Name): """ Number of ppendant_eight-knot cords in a Khipu """ return eight_knot_cords_df[(eight_knot_cords_df['KFG_Name'] == aKFG_Name) & (eight_knot_cords_df['is_pendant'])].shape[0]def num_subsidiary_eight_knot_cords(aKFG_Name): """ Number of ppendant_eight-knot cords in a Khipu """ return eight_knot_cords_df[(eight_knot_cords_df['KFG_Name'] == aKFG_Name) & (eight_knot_cords_df['is_pendant']==False)].shape[0]def num_sole_eight_knot_cords(aKFG_Name): """ Number of sole eight-knot cords in a Khipu """ kfg_df = eight_knot_cords_df[eight_knot_cords_df['KFG_Name'] == aKFG_Name] return kfg_df[kfg_df['tag'] == 'Sole_Eight_Knot'].shape[0]def num_multiple_only_eight_knot_cords(aKFG_Name): """ Number of only eight-knot cords in a Khipu """ kfg_df = eight_knot_cords_df[eight_knot_cords_df['KFG_Name'] == aKFG_Name] return kfg_df[kfg_df['tag'] == 'Multiple_Only_Eight_Knots'].shape[0]def num_mixed_eight_knot_cords(aKFG_Name): """ Number of mixed eight-knot cords in a Khipu """ kfg_df = eight_knot_cords_df[eight_knot_cords_df['KFG_Name'] == aKFG_Name] return kfg_df[kfg_df['tag'] == 'Mixed_Eight_Knot'].shape[0]def num_leading_eight_knot_cords(aKFG_Name): """ Number of leading eight-knot cords in a Khipu """ kfg_df = eight_knot_cords_df[eight_knot_cords_df['KFG_Name'] == aKFG_Name] return kfg_df[kfg_df['tag'] == 'Leading_Eight_Knot'].shape[0]def num_middle_eight_knot_cords(aKFG_Name): """ Number of middle eight-knot cords in a Khipu """ kfg_df = eight_knot_cords_df[eight_knot_cords_df['KFG_Name'] == aKFG_Name] return kfg_df[kfg_df['tag'] == 'Middle_Eight_Knot'].shape[0]def num_trailing_eight_knot_cords(aKFG_Name): """ Number of trailing eight-knot cords in a Khipu """ kfg_df = eight_knot_cords_df[eight_knot_cords_df['KFG_Name'] == aKFG_Name] return kfg_df[kfg_df['tag'] == 'Trailing_Eight_Knot'].shape[0]sole_eight_knots_df= eight_knot_cords_df[eight_knot_cords_df['tag'] == 'Sole_Eight_Knot']multiple_only_eight_knots_df = eight_knot_cords_df[eight_knot_cords_df['tag'] == 'Multiple_Only_Eight_Knots']mixed_eight_knots_df = eight_knot_cords_df[eight_knot_cords_df['tag'] == 'Mixed_Eight_Knot']leading_eight_knots_df = eight_knot_cords_df[eight_knot_cords_df['tag'] == 'Leading_Eight_Knot']middle_eight_knots_df = eight_knot_cords_df[eight_knot_cords_df['tag'] == 'Middle_Eight_Knot']trailing_eight_knots_df = eight_knot_cords_df[eight_knot_cords_df['tag'] == 'Trailing_Eight_Knot']num_eight_knots_dict = uloom.sort_dict_by_values({KFG_Name: num_eight_knot_cords(KFG_Name) for KFG_Name in KFG_Names if num_eight_knot_cords(KFG_Name) > 0})num_sole_eight_knots_dict = uloom.sort_dict_by_values({KFG_Name: num_sole_eight_knot_cords(KFG_Name) for KFG_Name in KFG_Names if num_sole_eight_knot_cords(KFG_Name) > 0})num_multiple_only_eight_knot_dict = uloom.sort_dict_by_values({KFG_Name: num_multiple_only_eight_knot_cords(KFG_Name) for KFG_Name in KFG_Names if num_multiple_only_eight_knot_cords(KFG_Name) > 0})num_mixed_eight_knots_dict = uloom.sort_dict_by_values({KFG_Name: num_mixed_eight_knot_cords(KFG_Name) for KFG_Name in KFG_Names if num_mixed_eight_knot_cords(KFG_Name) > 0})num_leading_eight_knots_dict = uloom.sort_dict_by_values({KFG_Name: num_leading_eight_knot_cords(KFG_Name) for KFG_Name in KFG_Names if num_leading_eight_knot_cords(KFG_Name) > 0})num_middle_eight_knots_dict = uloom.sort_dict_by_values({KFG_Name: num_middle_eight_knot_cords(KFG_Name) for KFG_Name in KFG_Names if num_middle_eight_knot_cords(KFG_Name) > 0})num_trailing_eight_knots_dict = uloom.sort_dict_by_values({KFG_Name: num_trailing_eight_knot_cords(KFG_Name) for KFG_Name in KFG_Names if num_trailing_eight_knot_cords(KFG_Name) > 0})```
3.2 Sole Eight-Knot Khipus
Code
```{python}the_num_sole_eight_knot_khipus = sole_eight_knots_df['KFG_Name'].nunique()the_num_sole_eight_knot_cords = sole_eight_knots_df.shape[0]print(f"{uloom.as_percent_string(the_num_sole_eight_knot_khipus, len(KFG_Names))} ({the_num_sole_eight_knot_khipus}/{len(KFG_Names)}) of Khipus have cords with a Sole Eight-Knot")print(f"{uloom.as_percent_string(the_num_sole_eight_knot_cords, total_num_khipu_cords)} ({the_num_sole_eight_knot_cords}/{total_num_khipu_cords}) of Cords have a Sole Eight-Knot\n")print(f"Top 5 Khipus with the most Cords with a Sole Eight-Knot")print(f"-------------------------------------------------------")for KFG_Name in list(num_sole_eight_knots_dict.keys())[:5]: the_num_kfg_sole_eight_knot_cords = num_sole_eight_knots_dict[KFG_Name] frequency_str = f"{KFG_Name}: {uloom.as_percent_string(the_num_kfg_sole_eight_knot_cords, total_num_khipu_cords)} ({the_num_kfg_sole_eight_knot_cords:4d} of {total_num_khipu_cords:4d})" print(f"{frequency_str} of its Cords have a Sole Eight-Knot")```
56.4% (401/711) of Khipus have cords with a Sole Eight-Knot
10.0% (6284/62837) of Cords have a Sole Eight-Knot
Top 5 Khipus with the most Cords with a Sole Eight-Knot
-------------------------------------------------------
KH0239: 0.4% ( 278 of 62837) of its Cords have a Sole Eight-Knot
KH0242: 0.4% ( 222 of 62837) of its Cords have a Sole Eight-Knot
KH0329: 0.3% ( 183 of 62837) of its Cords have a Sole Eight-Knot
KH0323: 0.3% ( 173 of 62837) of its Cords have a Sole Eight-Knot
KH0034: 0.3% ( 170 of 62837) of its Cords have a Sole Eight-Knot
3.3 Trailing Eight-Knot Khipus
A Trailing Figure-8-Knot is a cord with at least two knots, for which the last knot is a figure-8-knot.
Code
```{python}the_num_trailing_eight_knot_khipus = trailing_eight_knots_df['KFG_Name'].nunique()the_num_trailing_eight_knot_cords = trailing_eight_knots_df.shape[0]print(f"{uloom.as_percent_string(the_num_trailing_eight_knot_khipus, len(KFG_Names))} ({the_num_trailing_eight_knot_khipus}/{len(KFG_Names)}) of Khipus have cords with Trailing Figure-Eight-Knots")print(f"{uloom.as_percent_string(the_num_trailing_eight_knot_cords, total_num_khipu_cords)} ({the_num_trailing_eight_knot_cords}/{total_num_khipu_cords}) of Cords have Trailing Leading Eight-Knots\n")print(f"Top 5 Khipus with the most Cords with Trailing-Eight-Knots")print(f"----------------------------------------------------------")for KFG_Name in list(num_trailing_eight_knots_dict.keys())[:5]: the_num_kfg_trailing_eight_knot_cords = num_trailing_eight_knot_cords(KFG_Name) frequency_str = f"{KFG_Name}: {uloom.as_percent_string(the_num_kfg_trailing_eight_knot_cords, total_num_khipu_cords)} ({the_num_kfg_trailing_eight_knot_cords:4d} of {total_num_khipu_cords:4d})" print(f"{frequency_str} of its Cords have a Trailing Figure-Eight-Knots")```
59.1% (420/711) of Khipus have cords with Trailing Figure-Eight-Knots
4.0% (2515/62837) of Cords have Trailing Leading Eight-Knots
Top 5 Khipus with the most Cords with Trailing-Eight-Knots
----------------------------------------------------------
KH0517: 0.1% ( 72 of 62837) of its Cords have a Trailing Figure-Eight-Knots
KH0507: 0.1% ( 68 of 62837) of its Cords have a Trailing Figure-Eight-Knots
KH0698: 0.1% ( 48 of 62837) of its Cords have a Trailing Figure-Eight-Knots
KH0699: 0.1% ( 41 of 62837) of its Cords have a Trailing Figure-Eight-Knots
KH0516: 0.1% ( 38 of 62837) of its Cords have a Trailing Figure-Eight-Knots
What are the knot sequences for trailing-eight-knot cords?
Code
```{python}trailing_8knot_cords = []for index, row in trailing_eight_knots_df.iterrows(): aCord = khipu_dict[row['KFG_Name']][row['cord_name']] trailing_8knot_cords.append(aCord)trailing_8knot_cords_8knot__sequence_counts = Counter([aCord.knot_sequence() for aCord in trailing_8knot_cords])most_common_trailing_eight_knot_sequences = trailing_8knot_cords_8knot__sequence_counts.most_common()trailing_counts = [(index, count) for index, count in enumerate(trailing_8knot_cords_8knot__sequence_counts)][::-1]fig = (go.Figure(go.Bar( x=[item[1] for item in trailing_counts], y=[item[0] for item in trailing_counts], orientation='v', )))fig.layout.update(width=950, height=600, title_text='Trailing Knot Sequence Frequency')fig.update_layout( font_family="Lucida Grande", font_color="black", title_font_family="Lucida Grande", title_font_color="black", legend_title_font_color="black")fig.show()```
3.4 Leading Eight-Knot Khipus
A Leading Figure-8-Knot is a cord with at least two knots, of which the first one is a figure-8-knot (to distinguish this from a sole figure-8-knot).
Code
```{python}the_num_leading_eight_knot_khipus = leading_eight_knots_df['KFG_Name'].nunique()the_num_leading_eight_knot_cords = leading_eight_knots_df.shape[0]print(f"{uloom.as_percent_string(the_num_leading_eight_knot_khipus, len(KFG_Names))} ({the_num_leading_eight_knot_khipus}/{len(KFG_Names)}) of Khipus have cords with Leading Figure-Eight-Knots")print(f"{uloom.as_percent_string(the_num_leading_eight_knot_cords, total_num_khipu_cords)} ({the_num_leading_eight_knot_cords}/{total_num_khipu_cords}) of Cords have Mixed Leading Eight-Knots\n")print(f"Top 5 Khipus with the most Cords with Leading Eight-Knots")print(f"---------------------------------------------------------")for KFG_Name in list(num_leading_eight_knots_dict.keys())[:5]: the_num_kfg_leading_eight_knot_cords = num_leading_eight_knots_dict[KFG_Name] frequency_str = f"{KFG_Name}: {uloom.as_percent_string(the_num_kfg_leading_eight_knot_cords, total_num_khipu_cords)} ({the_num_kfg_leading_eight_knot_cords:4d} of {total_num_khipu_cords:4d})" print(f"{frequency_str} of its Cords have a Leading Figure-Eight-Knots")```
8.3% (59/711) of Khipus have cords with Leading Figure-Eight-Knots
0.2% (155/62837) of Cords have Mixed Leading Eight-Knots
Top 5 Khipus with the most Cords with Leading Eight-Knots
---------------------------------------------------------
KH0441: 0.0% ( 27 of 62837) of its Cords have a Leading Figure-Eight-Knots
KH0108: 0.0% ( 20 of 62837) of its Cords have a Leading Figure-Eight-Knots
KH0225: 0.0% ( 9 of 62837) of its Cords have a Leading Figure-Eight-Knots
KH0226: 0.0% ( 9 of 62837) of its Cords have a Leading Figure-Eight-Knots
KH0090: 0.0% ( 8 of 62837) of its Cords have a Leading Figure-Eight-Knots
What are the knot sequences for leading-eight-knot cords?
Code
```{python}leading_8knot_cords = []for index, row in leading_eight_knots_df.iterrows(): aCord = khipu_dict[row['KFG_Name']][row['cord_name']] leading_8knot_cords.append(aCord)leading_8knot_sequence_counts = Counter([aCord.knot_sequence() for aCord in leading_8knot_cords])most_common_leading_eight_knot_sequences = leading_8knot_sequence_counts.most_common()leading_8knot_counts = [(count, knot_sequence) for index, (knot_sequence, count) in enumerate(most_common_leading_eight_knot_sequences)]fig = (go.Figure(go.Bar( x=[item[1] for item in leading_8knot_counts], y=[item[0] for item in leading_8knot_counts], orientation='v', )))fig.layout.update(width=950, height=600, title_text='Leading 8-Knot Knot Sequence Frequency')fig.update_layout( font_family="Lucida Grande", font_color="black", title_font_family="Lucida Grande", title_font_color="black", legend_title_font_color="black")fig.show()```
3.5 Middle Eight-Knot Khipus
A Middle Figure-8-Knot is a cord with at least three knots, of which any knot that is not the leading or trailing knot, is a figure-8-knot.
Code
```{python}the_num_middle_eight_knot_khipus = middle_eight_knots_df['KFG_Name'].nunique()the_num_middle_eight_knot_cords = middle_eight_knots_df.shape[0]print(f"{uloom.as_percent_string(the_num_middle_eight_knot_khipus, len(KFG_Names))} ({the_num_middle_eight_knot_khipus}/{len(KFG_Names)}) of Khipus have cords with Middle Figure-Eight-Knots")print(f"{uloom.as_percent_string(the_num_middle_eight_knot_cords, total_num_khipu_cords)} ({the_num_middle_eight_knot_cords}/{total_num_khipu_cords}) of Cords have Middle Leading Eight-Knots\n")print(f"Top 5 Khipus with the most Cords with Middle Eight-Knots")print(f"--------------------------------------------------------")for KFG_Name in list(num_middle_eight_knots_dict.keys())[:5]: the_num_kfg_middle_eight_knot_cords = num_middle_eight_knots_dict[KFG_Name] frequency_str = f"{KFG_Name}: {uloom.as_percent_string(the_num_kfg_middle_eight_knot_cords, total_num_khipu_cords)} ({the_num_kfg_middle_eight_knot_cords:4d} of {total_num_khipu_cords:4d})" print(f"{frequency_str} of its Cords have a Middle Figure-Eight-Knots")```
5.9% (42/711) of Khipus have cords with Middle Figure-Eight-Knots
0.2% (112/62837) of Cords have Middle Leading Eight-Knots
Top 5 Khipus with the most Cords with Middle Eight-Knots
--------------------------------------------------------
KH0676: 0.1% ( 32 of 62837) of its Cords have a Middle Figure-Eight-Knots
KH0441: 0.0% ( 19 of 62837) of its Cords have a Middle Figure-Eight-Knots
KH0103: 0.0% ( 5 of 62837) of its Cords have a Middle Figure-Eight-Knots
KH0360: 0.0% ( 4 of 62837) of its Cords have a Middle Figure-Eight-Knots
KH0519: 0.0% ( 4 of 62837) of its Cords have a Middle Figure-Eight-Knots
What are the knot sequences for middle-eight-knot cords?
Code
```{python}middle_8knot_cords = []for index, row in middle_eight_knots_df.iterrows(): aCord = khipu_dict[row['KFG_Name']][row['cord_name']] middle_8knot_cords.append(aCord)middle_8knot_cords_8knot__sequence_counts = Counter([aCord.knot_sequence() for aCord in middle_8knot_cords])most_common_middle_eight_knot_sequences = middle_8knot_cords_8knot__sequence_counts.most_common()#for (index, (knot_sequence, count)) in enumerate(most_common_middle_eight_knot_sequences):# print(f"{index:02d}. {knot_sequence} | {count}")middle_8knot_counts = [(count, knot_sequence) for index, (knot_sequence, count) in enumerate(most_common_middle_eight_knot_sequences)]fig = (go.Figure(go.Bar( x=[item[1] for item in middle_8knot_counts], y=[item[0] for item in middle_8knot_counts], orientation='v', )))fig.layout.update(width=950, height=600, title_text='Middle 8-Knot Sequence Frequency')fig.update_layout( font_family="Lucida Grande", font_color="black", title_font_family="Lucida Grande", title_font_color="black", legend_title_font_color="black")fig.show()```
3.6 Multiple Only Eight-Knot Khipus
A Multiple Only Figure-8-Knot cord is a cord with at least two knots (to distinguish this from a sole figure-8-knot), and all knots are figure-8-knots
Code
```{python}the_num_multiple_only_eight_knot_khipus = multiple_only_eight_knots_df['KFG_Name'].nunique()the_num_multiple_only_eight_knot_cords = multiple_only_eight_knots_df.shape[0]print(f"{uloom.as_percent_string(the_num_multiple_only_eight_knot_khipus, len(KFG_Names))} ({the_num_multiple_only_eight_knot_khipus}/{len(KFG_Names)}) of Khipus have Cords with Multiple and Only Figure-Eight-Knots")print(f"{uloom.as_percent_string(the_num_multiple_only_eight_knot_cords, total_num_khipu_cords)} ({the_num_multiple_only_eight_knot_cords}/{total_num_khipu_cords}) of Cords have Multiple and Only Figure-Eight-Knots\n")print(f"Top 5 Khipus with the most Cords with Multiple Only Eight-Knots")print(f"---------------------------------------------------------------")for KFG_Name in list(num_multiple_only_eight_knot_dict.keys())[:5]: the_num_kfg_multiple_only_eight_knot_cords = num_multiple_only_eight_knot_dict[KFG_Name] frequency_str = f"{KFG_Name}: {uloom.as_percent_string(the_num_kfg_multiple_only_eight_knot_cords, total_num_khipu_cords)} ({the_num_kfg_multiple_only_eight_knot_cords:4d} of {total_num_khipu_cords:4d})" print(f"{frequency_str} of its Cords have a Multiple Only Figure-Eight-Knots")```
7.6% (54/711) of Khipus have Cords with Multiple and Only Figure-Eight-Knots
0.2% (144/62837) of Cords have Multiple and Only Figure-Eight-Knots
Top 5 Khipus with the most Cords with Multiple Only Eight-Knots
---------------------------------------------------------------
KH0702: 0.0% ( 15 of 62837) of its Cords have a Multiple Only Figure-Eight-Knots
KH0619: 0.0% ( 12 of 62837) of its Cords have a Multiple Only Figure-Eight-Knots
KH0108: 0.0% ( 9 of 62837) of its Cords have a Multiple Only Figure-Eight-Knots
KH0419: 0.0% ( 8 of 62837) of its Cords have a Multiple Only Figure-Eight-Knots
KH0239: 0.0% ( 7 of 62837) of its Cords have a Multiple Only Figure-Eight-Knots
Code
```{python}multiple_8knot_cords = []for index, row in multiple_only_eight_knots_df.iterrows(): aCord = khipu_dict[row['KFG_Name']][row['cord_name']] multiple_8knot_cords.append(aCord)multiple_8knot_cords_8knot__sequence_counts = Counter([aCord.knot_sequence() for aCord in multiple_8knot_cords])most_common_multiple_eight_knot_sequences = multiple_8knot_cords_8knot__sequence_counts.most_common()#for (index, (knot_sequence, count)) in enumerate(most_common_multiple_eight_knot_sequences):# print(f"{index:02d}. {knot_sequence} | {count}")multiple_8knot_counts = [(count, knot_sequence) for index, (knot_sequence, count) in enumerate(most_common_multiple_eight_knot_sequences)]fig = (go.Figure(go.Bar( x=[item[1] for item in multiple_8knot_counts], y=[item[0] for item in multiple_8knot_counts], orientation='v', )))fig.layout.update(width=950, height=600, title_text='Multiple 8-Knot Sequence Frequency')fig.update_layout( font_family="Lucida Grande", font_color="black", title_font_family="Lucida Grande", title_font_color="black", legend_title_font_color="black")fig.show()```
3.7 Mixed Eight-Knot Khipus
Cords that have Mixed Eight-Knots (i.e Leading + Middle, or Middle + Trailing or Leading + Trailing or Leading + Middle + Trailing, but not Only Eight-Knots)
Code
```{python}the_num_mixed_eight_knot_khipus = mixed_eight_knots_df['KFG_Name'].nunique()the_num_mixed_eight_knot_cords = mixed_eight_knots_df.shape[0]print(f"{uloom.as_percent_string(the_num_mixed_eight_knot_khipus, len(KFG_Names))} ({the_num_mixed_eight_knot_khipus}/{len(KFG_Names)}) of Khipus have cords with Mixed Figure-Eight-Knots")print(f"{uloom.as_percent_string(the_num_mixed_eight_knot_cords, total_num_khipu_cords)} ({the_num_mixed_eight_knot_cords}/{total_num_khipu_cords}) of Cords have Mixed Figure-Eight-Knots\n")print(f"Top 5 Khipus with the most Cords with Mixed Eight-Knots")print(f"-------------------------------------------------------")for KFG_Name in list(num_mixed_eight_knots_dict.keys())[:5]: the_num_kfg_mixed_eight_knot_cords = num_mixed_eight_knots_dict[KFG_Name] frequency_str = f"{KFG_Name}: {uloom.as_percent_string(the_num_kfg_mixed_eight_knot_cords, total_num_khipu_cords)} ({the_num_kfg_mixed_eight_knot_cords:4d} of {total_num_khipu_cords:4d})" print(f"{frequency_str} of its Cords have a Mixed Figure-Eight-Knots")```
6.5% (46/711) of Khipus have cords with Mixed Figure-Eight-Knots
0.2% (134/62837) of Cords have Mixed Figure-Eight-Knots
Top 5 Khipus with the most Cords with Mixed Eight-Knots
-------------------------------------------------------
KH0676: 0.0% ( 17 of 62837) of its Cords have a Mixed Figure-Eight-Knots
KH0517: 0.0% ( 13 of 62837) of its Cords have a Mixed Figure-Eight-Knots
KH0441: 0.0% ( 10 of 62837) of its Cords have a Mixed Figure-Eight-Knots
KH0507: 0.0% ( 9 of 62837) of its Cords have a Mixed Figure-Eight-Knots
KH0103: 0.0% ( 6 of 62837) of its Cords have a Mixed Figure-Eight-Knots
What are the knot sequences for mixed-eight-knot cords?
Code
```{python}mixed_8knot_cords = []for index, row in mixed_eight_knots_df.iterrows(): aCord = khipu_dict[row['KFG_Name']][row['cord_name']] mixed_8knot_cords.append(aCord)mixed_8knot__sequence_counts = Counter([aCord.knot_sequence() for aCord in mixed_8knot_cords])most_common_mixed_eight_knot_sequences = mixed_8knot__sequence_counts.most_common()#for (index, (knot_sequence, count)) in enumerate(most_common_mixed_eight_knot_sequences):# print(f"{index:02d}. {knot_sequence} | {count}")mixed_8knot_counts = [(count, knot_sequence) for index, (knot_sequence, count) in enumerate(most_common_mixed_eight_knot_sequences)]fig = (go.Figure(go.Bar( x=[item[1] for item in mixed_8knot_counts], y=[item[0] for item in mixed_8knot_counts], orientation='v', )))fig.layout.update(width=950, height=600, title_text='Mixed 8-Knot Sequence Frequency')fig.update_layout( font_family="Lucida Grande", font_color="black", title_font_family="Lucida Grande", title_font_color="black", legend_title_font_color="black")fig.show()```
3.8 Summary of Eight-Knots Cord Distribution
For this study, we move mixed figure-8-knots to the leading or trailing figure-8-knots category. We then get the following distribution for figure-8-knots based on cord knot-sequence type:
Code
```{python}def has_leading_eight_knot(aCord): """ Does the cord have multiple knots, and the first knot is an eight-knot and it's not only eight-knots? """ search_knots = aCord.all_knots() return (len(search_knots) >= 2) and search_knots[0].is_eight_knot()def has_middle_eight_knot(aCord): """ Does the cord have at least 3 knots, and at least one of the middle knots is an eight-knot and it's not only eight-knots? """ search_knots = aCord.all_knots() return (len(search_knots) >= 3) and any([aKnot.is_eight_knot() for aKnot in search_knots[1:-1]])def has_trailing_eight_knot(aCord): """ Does the cord have multiple knots, and the last knot is an eight-knot and it's not only eight-knots? """ search_knots = aCord.all_knots() return (len(search_knots) >= 2) and search_knots[-1].is_eight_knot()def tag_cord_knots(aCord): """ Tag the knots on a cord with an eight-knot type """ tag = "None" if has_sole_eight_knot(aCord): tag = "Sole_Eight_Knot" elif has_trailing_eight_knot(aCord): tag = "Trailing_Eight_Knot" elif has_leading_eight_knot(aCord): tag = "Leading_Eight_Knot" elif has_mutiple_only_eight_knots(aCord): tag = "Multiple_Only_Eight_Knots" elif has_middle_eight_knot(aCord): tag = "Middle_Eight_Knot" elif has_mixed_eight_knot(aCord): tag = "Mixed_Eight_Knot" return tageight_knot_cords_df = tag_eight_knot_cords()sole_eight_knots_df= eight_knot_cords_df[eight_knot_cords_df['tag'] == 'Sole_Eight_Knot']trailing_eight_knots_df = eight_knot_cords_df[eight_knot_cords_df['tag'] == 'Trailing_Eight_Knot']only_eight_knots_df = eight_knot_cords_df[eight_knot_cords_df['tag'] == 'Only_Eight_Knot']leading_eight_knots_df = eight_knot_cords_df[eight_knot_cords_df['tag'] == 'Leading_Eight_Knot']middle_eight_knots_df = eight_knot_cords_df[eight_knot_cords_df['tag'] == 'Middle_Eight_Knot']mixed_eight_knots_df = eight_knot_cords_df[eight_knot_cords_df['tag'] == 'Mixed_Eight_Knot']the_num_eight_knot_khipus = eight_knot_cords_df['KFG_Name'].nunique()the_num_sole_eight_knot_khipus = sole_eight_knots_df['KFG_Name'].nunique()the_num_trailing_eight_knot_khipus = trailing_eight_knots_df['KFG_Name'].nunique()the_num_leading_eight_knot_khipus = leading_eight_knots_df['KFG_Name'].nunique()the_num_middle_eight_knot_khipus = middle_eight_knots_df['KFG_Name'].nunique()the_num_mixed_eight_knot_khipus = mixed_eight_knots_df['KFG_Name'].nunique()the_num_eight_knot_cords = eight_knot_cords_df.shape[0]the_num_trailing_eight_knot_cords = trailing_eight_knots_df.shape[0]the_num_sole_eight_knot_cords = sole_eight_knots_df.shape[0]the_num_multiple_only_eight_knot_cords = only_eight_knots_df.shape[0]the_num_leading_eight_knot_cords = leading_eight_knots_df.shape[0]the_num_middle_eight_knot_cords = middle_eight_knots_df.shape[0]the_num_mixed_eight_knot_cords = mixed_eight_knots_df.shape[0]```
15% (9344 of 62837) (# 8-knot cords vs # All khipu cords)
61% (5655 of 9344)
39% (3689 of 9344)
Sole Figure 8 Knot Cords
56% (401 of 711)
67% (6284 of 9344)
52% (3294 of 6284)
48% (2990 of 6284)
Trailing Figure 8 Knot Cords
60% (428 of 711)
30% (2772 of 9344)
78% (2170 of 2772)
22% (602 of 2772)
Leading Figure 8 Knot Cords
8% (60 of 711)
2% (176 of 9344)
69% (122 of 176)
31% (54 of 176)
4 Figure-8-Knot Locations Over Groups
We now have an understanding of the distribution of Figure-8-Knots over khipu cords, and already see a distinction between “Lockean” and “Non-Lockean” knot sequences. Next, lets examine the location of cords with a Figure-8-Knot over khipu groups. We’ll look at cord locations across groups and then across khipus. If Figure-8-Knots play a “semiotic” role as a sign “marker”, then they should have a higher probability of occuring at the left and right edges of a group, and as might be expected, they do.
4.1 By Distance from Left or Right Edge of a Group:
Because khipus have different group sizes, we have to “sneak up” to our understanding of the probability of Figure-8-Knots occuring at the left/leading edge of a cord group vs the right/trailing edge of a cord group. First we simply look at the frequency of Figure-8-Knots at the left/leading edge of a cord group vs the right/trailing edge of a cord group. Then we “normalize” the locations by mapping the group size to a unit interval (0 to 1). For the purposes of this analysis, we’ll skip groups with only one cord.
4.1.1 By Distance from Leading/Trailing Edge
Let’s examine how often Figure-8-Knot Cords occur at the left/leading edge of a cord group vs the right/trailing edge of a cord group.
```{python}def is_left_half_cord(aCord): """ Is the cord in the left half of the khipu? """ return in_left_half(aCord.find_pendant_cord().cord_group.position_index, aCord.find_pendant_cord().cord_group.num_pendant_cords(), include_middle=False)figure_8_knot_cord_in_group_locs = [aCord.find_pendant_cord().group_index()+1 for aCord in all_figure_8_knot_cords if is_left_half_cord(aCord)] fig = (px.histogram(figure_8_knot_cord_in_group_locs, nbins=160, width=944, height=500, title='Histogram of Figure-8-Knot Left-Half Cord Locations by LEADING Position from Left in a Group', labels={'value':'Locations by LEADING Cord Position (from Left) in a Group'}, ) .update_layout( showlegend=False, font_family="Lucida Grande", font_color="black", font_size=12, title_font_family="Lucida Grande", title_font_color="black", legend_title_font_color="black") .show()) ```
Code
```{python}def trailing_position(aCord): num_group_cords = aCord.find_pendant_cord().cord_group.num_pendant_cords() cord_index = aCord.find_pendant_cord().group_index() + 1 the_position = -(num_group_cords - cord_index) return the_positiondef is_right_half_cord(aCord): """ Is the cord in the left half of the khipu? """ return in_right_half(aCord.find_pendant_cord().cord_group.position_index, aCord.find_pendant_cord().cord_group.num_pendant_cords(), include_middle=False)figure_8_knot_cord_in_group_locs = [trailing_position(aCord) for aCord in all_figure_8_knot_cords if is_right_half_cord(aCord)] fig = (px.histogram(figure_8_knot_cord_in_group_locs, nbins=160, width=944, height=500, title='Histogram of Figure-8-Knot Right-Half Cord Locations by TRAILING Position from Right in a Group', labels={'value':'Locations by Trailing Cord Position (from Right) in a Group'}, ) .update_layout( showlegend=False, font_family="Lucida Grande", font_color="black", font_size=12, title_font_family="Lucida Grande", title_font_color="black", legend_title_font_color="black") .show())```
4.1.2 By Normalized Location Within a Group
Code
```{python}normalized_8knot_cord_in_group_locs = [float(aCord.find_pendant_cord().group_index())/float(aCord.cord_group.num_pendant_cords()-1) for aCord in all_figure_8_knot_cords if aCord.cord_group.num_pendant_cords() > 1] fig = (px.histogram(normalized_8knot_cord_in_group_locs, nbins=100, histnorm="probability", width=944, height=500, title=f'Probability of Figure-8-Knot Normalized Cord Locations over a Group where #Pendants > 1') .update_layout( xaxis_title="Normalized Cord Location (0 to 1)", yaxis_title="Probability of Figure-8-Knot Cord", showlegend=False, font_family="Lucida Grande", font_color="black", font_size=12, title_font_family="Lucida Grande", title_font_color="black", legend_title_font_color="black") .show())```
The probability distribution shows that, Figure-8-Knots congregate at the left, right, and middle locations of a group.
The first cord of a group has a high probability of containing a Figure-8-Knot
The last cord of a group has ~85% of the probability of the first cord containing a Figure-8-Knot
The middle cord has a medium probability it will contain a Figure-8-Knot.
Figure-8-Knot cord frequencies also spike at 1/3 and 2/3 of the way through a group.
4.2 By Group Size
Another understanding of how figure 8 knots locate is to graph the counts for each group size (2,3,4,…) for locations of Figure-8-Knot cords.
The first graph shows the frequency count of Figure-8-Knot cords for groups of different sizes. To see critical detail we clip the 5 outlier khipus with more than 80 pendant cords
Code
```{python}max_size = 80num_groups_by_size = [0] * (max_size+1)for aCord in all_figure_8_knot_cords: cords_group_size = aCord.find_pendant_cord().cord_group.num_pendant_cords() if cords_group_size <= max_size: num_groups_by_size[cords_group_size] += 1group_size_counts = [(index, count) for index, count in enumerate(num_groups_by_size) if count > 0] fig = (go.Figure(go.Bar( x=[item[1] for item in group_size_counts], y=[item[0] for item in group_size_counts], orientation='h', )))fig.update_layout(width=950, height=1200, yaxis=dict( dtick=5, # tick spacing tick0=0, # where ticks start from range=[0, 80]), xaxis_title="Frequency of Figure-8-Knot Cords", yaxis_title="Group Size (# Pendant Cords)", title_text='Groups with Figure8KnotCords - Frequency of Group Size (# Pendant Cords)', font_family="Lucida Grande", font_color="black", title_font_family="Lucida Grande", title_font_color="black", legend_title_font_color="black" )fig.show()```
Code
```{python}import warningswarnings.filterwarnings("ignore", category=FutureWarning)def make_figure8knot_by_group_size_df(max_size=None): if max_size is None: max_size = max([aCord.cord_group.num_pendant_cords() for aCord in all_figure_8_knot_cords]) fig_8_knot_table = [[None] * (max_size+4) for i in range((max_size+1))] for aCord in all_figure_8_knot_cords: row_index = aCord.cord_group.num_pendant_cords() col_index = aCord.find_pendant_cord().group_index()+1 in_max_bounds = (row_index < max_size) and (col_index < max_size) in_min_bounds = (row_index > 0) and (col_index > 0) if in_min_bounds and in_max_bounds: fig_8_knot_table[row_index][col_index] = fig_8_knot_table[row_index][col_index]+1 if (not(fig_8_knot_table[row_index][col_index]) is None) else 1 df = pd.DataFrame(fig_8_knot_table) return dfmax_size = 90df = make_figure8knot_by_group_size_df(max_size)df.to_csv(f"{uloc.fieldmarks_data_dir()}/figure8knot_by_group_size.csv")max_demo_size = 17demo_df = df.copy()demo_df = demo_df[demo_df.columns[:(max_demo_size+1)]][:(max_demo_size+1)].fillna(0)cols = demo_df.columns.tolist()demo_df[cols] = demo_df[cols].applymap(np.int64)demo_df=demo_df.replace(0,' ')demo_df["# Groups of Size"] = [0]+ [num_groups for (x,num_groups) in group_size_counts[:(max_demo_size)]]print(f"Top {max_demo_size} Figure-8-Knot Cord Counts, by Position, in Groups of up to {max_demo_size} Pendants in Size")demo_df.head(max_demo_size+2)```
Top 17 Figure-8-Knot Cord Counts, by Position, in Groups of up to 17 Pendants in Size
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Groups of Size
0
0
1
511
511
2
195
174
369
3
216
165
153
534
4
211
192
192
209
804
5
194
193
155
164
138
844
6
168
216
229
203
159
151
1126
7
67
100
105
92
114
88
46
612
8
80
115
109
109
97
79
64
85
738
9
114
80
78
82
86
83
74
84
93
774
10
80
70
68
65
67
59
73
71
69
48
670
11
27
37
38
29
38
41
37
39
47
34
29
396
12
40
37
43
52
40
27
31
37
41
40
30
46
464
13
6
2
5
6
4
9
7
15
11
12
10
5
10
102
14
17
17
19
18
15
17
16
15
13
17
16
17
23
9
229
15
12
12
20
12
10
12
14
18
15
10
13
11
11
15
31
216
16
13
24
17
14
18
13
12
14
14
14
13
13
17
11
8
18
233
17
8
12
10
8
9
19
14
14
17
6
17
12
9
9
11
10
4
189
Code
```{python}def draw_figure8knot_by_group_size(max_size=None, df=None): if df is None: df = make_figure8knot_by_group_size_df(max_size) fig = (px.imshow(df.to_numpy().tolist(), labels=dict(x="Cord Position From Left", y="# Group Pendant Cords", ), x=df.columns, y=df.index.tolist(), color_continuous_scale=px.colors.sequential.Viridis, width=944, height=944, aspect="auto") .update_coloraxes(showscale=True) .update_layout( showlegend=False, font_family="Lucida Grande", font_size=14, font_color="black", title_font_family="Lucida Grande", title_font_color="black", legend_title_font_color="black") .update_layout( yaxis = dict(tickfont = dict(size=8)), xaxis= dict(tickangle=270), title=f"Figure-8-Knot Cord Counts, by Position, in Groups up to {max_size} Pendants in Size") .show())draw_figure8knot_by_group_size(max_size)```
5 Figure-8-Knot Locations over Khipus:
To compute locations of cords, across khipus of different sizes, we compute a “normalized” location. The normalized location is the cord’s location divided by the total number of (pendant) cords in the khipu.
Then we can examine the (binned) histogram/distribution…of normalized locations.
Code
```{python}normalized_8knot_cord_locs = [float(aCord.pendant_index())/float(aCord.khipu.num_pendant_cords()-1) for aCord in all_figure_8_knot_cords if aCord.khipu.num_pendant_cords() > 1] fig = (px.histogram(normalized_8knot_cord_locs, nbins=100, width=944, height=600, histnorm="probability", title='Figure-8-Knot Normalized Cord Locations over a Khipu') .update_layout( xaxis_title="Normalized Cord Location (0 to 1)", yaxis_title="Probability of Figure-8-Knot Cord", showlegend=False, font_family="Lucida Grande", font_color="black", title_font_family="Lucida Grande", title_font_color="black", legend_title_font_color="black") .show())```
While Figure-8-Knots do spike in frequency at the beginning and end cord of a khipu, the above distribution seems closer to overall uniform noise.
Let’s broaden our scope to normalize by group rather than by cord. For all khipus with more than one cord group, let’s evaluate the cord group’s position:
Code
```{python}normalized_8knot_cord_group_locs = [float(aCord.find_pendant_cord().cord_group.position_index)/float(aCord.khipu.num_cord_groups()-1) for aCord in all_figure_8_knot_cords if aCord.khipu.num_cord_groups() > 1] fig = (px.histogram(normalized_8knot_cord_group_locs, nbins=100, histnorm="probability", width=944, height=500, title='Probability of Figure-8-Knot Normalized by Cord Group Locations over a Khipu') .update_layout( xaxis_title="Normalized Cord Group Location (0 to 1)", yaxis_title="Probability of 8-Knot in Group", showlegend=False, font_family="Lucida Grande", font_color="black", font_size=14, title_font_family="Lucida Grande", title_font_color="black", legend_title_font_color="black") .show())```
Fascinating. Now we see a significant spike in the probability of a Figure-8-Knot occuring in the first and last group of a khipu. In Brezinne and Urton’s article on the Puruchuco khipus, they note that a figure-8-knot frequently occurs at the beginning of the Puruchuco khipus, and that may signify a toponymic presence. We clearly see evidence of Figure-8-Knots playing some kind of semiotic role in the first group of the KFG database.
6. Conclusions
In section 1.3 Trailing vs Sole Digits it was shown that there is indeed a distinction in distribution of Sole digits vs Trailing digits on pendant vs subsidiary cords.
The most common value range is 2-9, represented as an L knot, occupies 21% of the cords.
The second most common value range 11-99, represented as S,L, occupies 14% of the cords.
The third most common value, the value 1, represented as a Sole Figure-8-Knot E, occupies 10% of the cords.
E Sole Eight Knots reside equally (52%/48%) on pendants vs subsidiaries. This is unlike any of the other knot sequences which are typically 3 to 1 or 4 to 1 ratios for pendants vs subsidiaries
S,E, S,S,E, L,E all have a Trailing Figure-Eight-Knot. and represent ~3% of the cords.
The value range 101-999, represented as S,S,L knot sequences are fifth on the list.
The probability distribution shows that, Figure-8-Knots congregate at the left, right, and middle locations of a group.
The first cord of a group has a high probability of containing a Figure-8-Knot
The last cord of a group has ~85% of the probability of the first cord containing a Figure-8-Knot
The middle cord has a medium probability it will contain a Figure-8-Knot.
Figure-8-Knot cord frequencies also spike at 1/3 and 2/3 of the way through a group.
Finally, in section 5 Figure-8-Knot Groups Within a Khipu it was shown that groups in khipus with figure 8 knots have a similar to cord-in-group location distribution. Significantly we confirmed that the leftmost and/or rightmost groups of a khipu have a significantly higher than random likelihood of containing a Figure-8-Knot in their cords.
6.1 Summary of Knot Sequences and Figure-8 Knot Sequences
The top knot sequences in the KFG database with more than 200 occurrences (out of ~60,000 cords) are:
Index
Knot Type Sequence
# Cords
# Pendant Cords
# Subsidiary Cords
-
-
59144 Cords
72% (42572 of 59144)
28% (16572 of 59144)
0
No Knots
30% (19058 of 62837)
74% (14195 of 19058)
26% (4863 of 19058)
1
L
21% (13000 of 62837)
64% (8317 of 13000)
36% (4683 of 13000)
2
S,L
14% (8509 of 62837)
80% (6801 of 8509)
20% (1708 of 8509)
3
E
10% (6212 of 62837)
52% (3253 of 6212)
48% (2959 of 6212)
4
S
9% (5963 of 62837)
72% (4293 of 5963)
28% (1670 of 5963)
5
S,S,L
4% (2617 of 62837)
89% (2332 of 2617)
11% (285 of 2617)
6
S,E
2% (1325 of 62837)
78% (1033 of 1325)
22% (292 of 1325)
7
S,S
2% (1127 of 62837)
84% (948 of 1127)
16% (179 of 1127)
8
L,L
1% (668 of 62837)
84% (558 of 668)
16% (110 of 668)
9
S,S,S,L
1% (586 of 62837)
94% (548 of 586)
6% (38 of 586)
10
L,L,L
1% (412 of 62837)
92% (380 of 412)
8% (32 of 412)
11
S,S,E
1% (338 of 62837)
89% (301 of 338)
11% (37 of 338)
12
L,E
0% (313 of 62837)
67% (211 of 313)
33% (102 of 313)
13
S,S,S
0% (250 of 62837)
82% (206 of 250)
18% (44 of 250)
14
S,L,E
0% (207 of 62837)
0% (0 of 207)
100% (207 of 207)
6.2 Summary of Eight-Knot Types
Knot Type
# Khipus
# Cords
Eight-Knot Exists
77% (550 of 711)
15% (9344 of 62837)
Sole Eight-Knot
56% (401 of 711)
67% (6284 of 9344)
Trailing Eight-Knot
60% (428 of 711)
30% (2772 of 9344)
Leading Eight-Knot
8% (60 of 711)
2% (176 of 9344)
Middle Eight-Knot
6% (42 of 711)
1% (112 of 9344)
From now on, we will focus on cases of either a cord with a trailing Figure-Eight-Knot, or a cord with a sole Figure-Eight-Knot. If it’s a mixed Figure-Eight-Knot, or multiple-only Figure-Eight-Knots it will be considered to be a leading and trailing Figure-Eight-Knot.
Accordingly, the new measures are:
Figure-8-Knot Type
% of Khipus
# Cords of that Type
% Of Pendant Cords
% of Subsidiary Cords
All Figure 8 Knot Cords
77% (550 of 711)
15% (9344 of 62837) (# 8-knot cords vs # All khipu cords)
61% (5655 of 9344)
39% (3689 of 9344)
Sole Figure 8 Knot Cords
56% (401 of 711)
67% (6284 of 9344)
52% (3294 of 6284)
48% (2990 of 6284)
Trailing Figure 8 Knot Cords
60% (428 of 711)
30% (2772 of 9344)
78% (2170 of 2772)
22% (602 of 2772)
Leading Figure 8 Knot Cords
8% (60 of 711)
2% (176 of 9344)
69% (122 of 176)
31% (54 of 176)
The apportionment of knot types, pendant/subsidiaries, etc., is summarized in the Sankey Diagram below:
Significant points:
77% of the khipus in the KFG have Figure-8-Knot cords
Only 15% of the cords in the KFG have Figure-8-Knot cords
There is a rough 2:1 ratio of Sole-8 Knot cords (i.e. a knot-sequence of ‘E’) to Trailing Figure-8-Knot cords (i.e. ‘S,E’, ‘S,L,E’, etc)
Sole-8-Knot cords occur roughly equally 1:1 on primaries and subsidiaries, but Trailing Figure-8-Knot cords have the more common 2:1 or 3:1 pendant to subsidiary frequency count.
Figure-8-Knot Sankey Diagram
6.3 Summary of 8 Knot Locations
At a khipu level, we see that:
Figure-8-Knot cords have a high probability of occuring in the first group or last group of a khipu.
The middle group has a medium probability it will contain a figure 8 knot cord.
Figure-8-Knot cord frequencies also spike at 1/4 and 3/4 of the way through the groups in the khipus.
Similarly by analogy, at a group level:
The first cord of a group has a high probability of containing a figure-8-Knot cords
The last cord of a group has ~3/4 of the probability of the first cord containing a figure-8-knot cord
The middle cord has a medium probability it will contain a figure 8 knot cord.
Figure-8-Knot cord frequencies also spike at 1/3 and 2/3 of the way through a group.
The following images layout the evidence for these claims: