Sum Top Cords


1. Number of Top Cord Sum Cords

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. KH0405 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 KH0405 for an example. That check and a review of the database shows that 21 khipus match, although KH0356 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 plotly
import plotly
plotly.offline.init_notebook_mode(connected = False);

# Read in the Fieldmark and its associated dataframe and match dictionary
from fieldmark_sum_top_cords import FieldmarkSumTopCords
aFieldmark = FieldmarkSumTopCords()
fieldmark_dataframe = aFieldmark.dataframes[0].dataframe
raw_match_dict = aFieldmark.raw_match_dict()
Code
# Bookkeeping to produce table at top of this page
from statistics import mean
import pandas as pd
import 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 in range(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 in range(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
KH0055 9 42 30 76 15 0 160
KH0358 7 460 369 581 122 0 1957
KH0109 7 27 12 60 5 0 60
KH0079 7 113 32 237 24 0 300
KH0217 6 376 50 587 104 0 922
KH0405 5 701 17 1417 244 0 1417
KH0008 5 126 80 152 16 0 170
KH0597 4 78 50 98 10 0 239
KH0359 4 423 390 481 85 0 643
KH0007 4 125 105 140 34 4 140
KH0424 3 250 75 596 38 0 596
KH0085 3 200 30 530 69 0 530
KH0002 2 97 93 101 27 3 101
KH0277 2 80 60 100 16 4 120
KH0602 2 738 541 935 45 0 935
KH0482 2 302 288 316 67 5 316
KH0323 2 15 15 16 3 0 26
KH0577 1 842 842 842 191 0 2643
KH0235 1 76 76 76 7 1 76
KH0004 1 76 76 76 21 4 76
KH0389 1 601 601 601 48 0 601
KH0603 1 512 512 512 248 0 2792
KH0005 1 312 312 312 246 10 932
KH0278 1 25 25 25 7 0 158
KH0068 1 2 2 2 1 0 104
KH0477 1 18 18 18 6 0 61
KH0607 1 173 173 173 30 0 601
KH0604 1 129 129 129 15 0 129
KH0003 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):
    if len(self.cord_groups) >= 2:
        for i in range(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:
                if self.cord_groups[i].num_cords() == 1:
                    self.cord_groups[i].tag_sum_group(self.cord_groups[i+1], is_exact_sum= exact_match)
                elif self.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 in self.off_by_one(right_sum) or right_sum in self.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)
    return list(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 in enumerate(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

4. Summary Results:

Measure Result
Number of Khipus That Match 22 (3%)
Number of Significant Khipus 22 (3%)
Five most Significant Khipu KH0055, KH0079, KH0109, KH0358, KH0217
Image Quilt Click here
Database View Click here

5. Summary Charts:

Code
from plotly import express as px

# Plot Matching khipu
matching_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 khipu
significant_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 uloom

sum_top_cord_df = aFieldmark.dataframes[1].dataframe
num_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_cords
num_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 Counter
num_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 khipu
matching_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.