The discovery by Leland Locke in 1926 of top cord values matching the sum of their adjacent group cords was a major breakthrough in khipu studies and confirmed the use of base 10 in Inkan-era khipus. UR166 set the stage for modern khipu studies.
Interestingly, the sums often don’t quite match, and are off by one digit, not just in the ones place, but sometimes in the tens, hundreds, or thousands place. The summer/yupana user, or the reader of the knots, erred - I suspect the latter.
Since the combinatorial search space is pretty small, (adjacent groups), I can check for off-by-one digit errors in the ones, tens, etc. count, indicating that the sum is approximate (~) on the rendered khipus - see our old friend UR166 for an example. That check and a review of the database shows that 21 khipus match, although UR117C probably does not count. That’s ~4% of the overall database. A significant number and clearly a useful fieldmark. Their distribution is as follows:
Code
# Initialize plotlyimport plotlyplotly.offline.init_notebook_mode(connected =False);# Read in the Fieldmark and its associated dataframe and match dictionaryfrom fieldmark_sum_top_cords import FieldmarkSumTopCordsaFieldmark = FieldmarkSumTopCords()fieldmark_dataframe = aFieldmark.dataframes[0].dataframeraw_match_dict = aFieldmark.raw_match_dict()
Code
# Bookkeeping to produce table at top of this pagefrom statistics import meanimport pandas as pdimport qollqa_chuspa as qc # Load khipus(khipu_dict, all_khipus) = qc.fetch_khipus()cord_value_and_location = []for khipu_name in aFieldmark.matching_khipus(): aKhipu = khipu_dict[khipu_name] top_cord_sum_groups = aKhipu.top_cord_sum_groups()for group in top_cord_sum_groups: top_cord_value = mean([aCord.knotted_value for aCord in group.pendant_cords()]) top_cord_location = mean([aCord.pendant_index() for aCord in group.pendant_cords()]) cord_value_and_location.append((khipu_name, top_cord_value, top_cord_location))top_cord_df = pd.DataFrame(cord_value_and_location, columns=['kfg_name', 'cord_value', 'location'])top_cord_dict = {}for row_index inrange(len(top_cord_df)): khipu_name = top_cord_df.kfg_name.values[row_index] cord_value = top_cord_df.cord_value.values[row_index]if khipu_name in top_cord_dict: top_cord_dict[khipu_name].append(cord_value)else: top_cord_dict[khipu_name]= [cord_value]top_cord_dict =dict(sorted(top_cord_dict.items(),key=lambda x:len(x[1])))top_cord_table =sorted([[kfg_name, str(len(top_cord_dict[kfg_name])), str(mean(top_cord_dict[kfg_name]))] for kfg_name in top_cord_dict], key =lambda x:(x[1], x[2]), reverse =True)top_cord_khipus = [top_cord_table[i][0] for i inrange(len(top_cord_dict))]top_cord_count = [len(top_cord_dict[khipu_name]) for khipu_name in top_cord_khipus]top_cord_mean = [mean(top_cord_dict[khipu_name]) for khipu_name in top_cord_khipus]top_cord_min = [min(top_cord_dict[khipu_name]) for khipu_name in top_cord_khipus]top_cord_max = [max(top_cord_dict[khipu_name]) for khipu_name in top_cord_khipus]top_cord_khipu_mean = [khipu_dict[khipu_name].mean_pendant_cord_value() for khipu_name in top_cord_khipus]top_cord_khipu_min = [khipu_dict[khipu_name].min_pendant_cord_value() for khipu_name in top_cord_khipus]top_cord_khipu_max = [khipu_dict[khipu_name].max_pendant_cord_value() for khipu_name in top_cord_khipus]top_cord_data =zip(top_cord_khipus, top_cord_count, top_cord_mean, top_cord_min, top_cord_max, top_cord_khipu_mean, top_cord_khipu_min, top_cord_khipu_max)colnames = ['Khipu Name', 'Num of Top Cords', 'Mean Top Cord', 'Min Top Cord', 'Max Top Cord', 'Mean Khipu Cord', 'Min Khipu Cord', 'Max Khipu Cord']top_cord_display_df = pd.DataFrame(top_cord_data, columns = colnames)
Khipu Name
Num Top Cords
Mean Top Cord
Min Top Cord
Max Top Cord
Mean Khipu Cord
Min Khipu Cord
Max Khipu Cord
AS044
9
42
30
76
15
0
160
UR118
7
460
369
581
122
0
1957
UR1096
7
27
12
60
5
0
60
AS066
7
113
32
237
24
0
300
AS199
6
376
50
587
104
0
922
UR166
5
701
17
1417
244
0
1417
AS007
5
126
80
152
16
0
170
QU009
4
78
50
98
10
0
239
UR119
4
423
390
481
85
0
643
AS006
4
125
105
140
34
4
140
UR186
3
250
75
596
38
0
596
AS072
3
200
30
530
69
0
530
AS001
2
97
93
101
27
3
101
UR047
2
80
60
100
16
4
120
QU014
2
738
541
935
45
0
935
UR246
2
302
288
316
67
5
316
UR087
2
15
15
16
3
0
26
HP047
1
842
842
842
191
0
2643
AS215
1
76
76
76
7
1
76
AS003
1
76
76
76
21
4
76
UR149
1
601
601
601
48
0
601
QU015
1
512
512
512
248
0
2792
AS004
1
312
312
312
246
10
932
UR048
1
25
25
25
7
0
158
UR1057
1
2
2
2
1
0
104
UR241
1
18
18
18
6
0
61
QU019
1
173
173
173
30
0
601
QU016
1
129
129
129
15
0
129
AS002
1
117
117
117
33
8
117
2. Search Criteria:
In this case, it’s pretty clear what we are searching for. The only wrinkle is that they khipu-kamayoq who made the khipu frequently was off by one in one digit - getting 468 instead of 469 or 569 instead of 469. That case has to be checked.
Code
# Code excerpts:def tag_sum_groups(self):iflen(self.cord_groups) >=2:for i inrange(len(self.cord_groups)-1): (sum_matches, exact_match) =self.cord_group_sums_match(self.cord_groups[i], self.cord_groups[i+1])if sum_matches:ifself.cord_groups[i].num_cords() ==1:self.cord_groups[i].tag_sum_group(self.cord_groups[i+1], is_exact_sum= exact_match)elifself.cord_groups[i+1].num_cords() ==1:self.cord_groups[i+1].tag_sum_group(self.cord_groups[i], is_exact_sum= exact_match)def cord_group_sums_match(self, left_group, right_group): left_sum = left_group.group_cord_sum() right_sum = right_group.group_cord_sum()if left_sum >0: # A lot of uninformatively tagged 0 sum cord groups. Much ado about nothing. if left_sum == right_sum:return (True, True)elif left_sum inself.off_by_one(right_sum) or right_sum inself.off_by_one(left_sum):return (True,False)else:return (False, False)else:return (False, False)def near_digits(self, aDigit): min_digit =max(0, aDigit-1) max_digit =min(9,aDigit+1)returnlist(range(min_digit, max_digit+1))def off_by_one(self, aNumber): digit_chars =list(f"{aNumber}") int_chars = [int(aChar) for aChar in digit_chars] near_combos = [self.near_digits(x) for x in int_chars] final_possibilities = []for i, aCombo inenumerate(near_combos):for possible_digit in aCombo: final_possibilities.append("".join(digit_chars[0:i]) +f"{possible_digit}"+"".join(digit_chars[i+1:])) off_by_one_possibilities = [int(aString) for aString in final_possibilities]return off_by_one_possibilities
3. Significance Criteria:
This is such a rare (and unambigous) fieldmark that any occurrence is deemed significant
from plotly import express as px# Plot Matching khipumatching_khipus = aFieldmark.matching_khipus() matching_values = [raw_match_dict[aKhipuName] for aKhipuName in matching_khipus]matching_df = pd.DataFrame(list(zip(matching_khipus, matching_values)), columns =['KhipuName', 'Value'])fig = px.bar(matching_df, x='KhipuName', y='Value', labels={"KhipuName": "Khipu Name", "Value": "Number of Top Cord Sum Cords", }, title=f"Matching Khipu ({len(matching_khipus)}) for Number of Top Cord Sum Cords", width=944, height=450).update_layout(showlegend=True).show()
Code
# Plot Significant khipusignificant_khipus = aFieldmark.significant_khipus()significant_values = [raw_match_dict[aKhipuName] for aKhipuName in significant_khipus]significant_df = pd.DataFrame(list(zip(significant_khipus, significant_values)), columns =['KhipuName', 'Value'])fig = px.bar(significant_df, x='KhipuName', y='Value', labels={"KhipuName": "Khipu Name", "Value": "Number of Top Cord Sum Cords", }, title=f"Significant Khipu ({len(significant_khipus)}) for Number of Top Cord Sum Cords", width=944, height=450).update_layout(showlegend=True).show()
6. Exploratory Data Analysis
Note that all double sum top cord khipus exist on khipus that also have “regular” top sum cords.
Code
print(f"A total of {sum(list(fieldmark_dataframe.num_sum_top_cords.values))} sum top cords exist over {len(matching_khipus)} khipus with sum top cords")
A total of 86 sum top cords exist over 22 khipus with sum top cords
6.1 Range and Position
Code
fig = (px.scatter(top_cord_df, x="location", y="cord_value", #color="handed_color", color_continuous_scale=['#3c3fff', '#ff3030',], labels={"location":"Top Cord Location From Left","cord_value":"Top Cord Sum Value"}, hover_name="kfg_name", hover_data=['location', 'cord_value'], title=f"<b>Top Cord Sums - Position vs. Sum Value</b>", width=944, height=944) .update_layout(showlegend=False) .update(layout_coloraxis_showscale=False) .show() )
6.2 Summand Cords
An examination of the sum top cord data reveals that many of the sum top cords are associated with a group of six pendant cords. How much? An astonishing 70%.
Code
import utils_loom as uloomsum_top_cord_df = aFieldmark.dataframes[1].dataframenum_summand_cords = []for kfg_name in sum_top_cord_df['kfg_name'].tolist(): kfg_top_cord_df = sum_top_cord_df[sum_top_cord_df.kfg_name == kfg_name] summand_string = kfg_top_cord_df.summand_string.values[0] num_summand_cords.append(len(aFieldmark.cords_from_sum_string(khipu_dict[kfg_name], summand_string, one_based=False)))sum_top_cord_df['num_summand_cords'] = num_summand_cordsnum_6_summands = sum_top_cord_df[sum_top_cord_df.num_summand_cords ==6].shape[0]num_8_summands = sum_top_cord_df[sum_top_cord_df.num_summand_cords ==8].shape[0]total_sum_cords = sum_top_cord_df[sum_top_cord_df.num_summand_cords >0].shape[0]from collections import Counternum_summands_counter = Counter(num_summand_cords)bar_plots_tuples = [(key, num_summands_counter[key]) for key in num_summands_counter if key >0] # Plot Matching khipumatching_df = pd.DataFrame(bar_plots_tuples, columns =['KhipuName', 'Value'])fig = px.bar(matching_df, x='KhipuName', y='Value', labels={"KhipuName": "Khipu Name", "Value": "Number of Top Cord Sum Cords", }, title=f"Matching Khipu ({len(matching_khipus)}) for Number of Top Cord Sum Cords", width=944, height=450).update_layout(showlegend=True).show()print(f"{uloom.percent_info(num_6_summands, total_sum_cords, rounddigits=0):} Sum Top Cords have 6 Summands")print(f"{uloom.percent_info(num_8_summands, total_sum_cords, rounddigits=0):} Sum Top Cords have 8 Summands")
47% (40 of 86) Sum Top Cords have 6 Summands
15% (13 of 86) Sum Top Cords have 8 Summands
7. Conclusion
22 (3%) of the KFG khipus have this relationship.
86 top cords exist. Less than 1% of pendant-pendant sum relationships. Clearly although top cord sums were the first sum relationship to be found (by Locke in 1926), they are rare.
47% (40 of 86) of the time the sum cords are associated with a sum group of 6 summand cords. 15% (13 of 86) Sum Top Cords have 8 Summands
Most sum top cords are less than 1000. Waranka scale.