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. 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 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
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):
    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 AS044, AS066, UR1096, UR118, AS199
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.