Subsidiary Pendant Sum Cords


1. Number of Subsidiary Pendant Sum Cords

Subsidiary cords that are Sums of a set of Primary cord Pendant cords.

2. Search Criteria:

Here again, there is a balance between precision and recall. We limit searches to where the subsidiary cord has a value of at least 5, and where the summand cords are contiguous. Furthermore, for summation candidates, the maximum moving search window size is set to 250 contiguous (except for 0-value cords) at a time.

In the never ending struggle to dial it just right, the search is limited to pendant cords with a value of at least 11, and where the summand cords are contiguous for at least three cords (not including 0 cords in-between). Furthermore, for summation candidates, the maximum moving search window size is set to 250 contiguous (except for 0-value cords) at a time.

What to do about off-by-one errors, where for example 483 should match 493? Although off-by-one knot errors occur frequently, in this particular study, unlike top cords, Sums are not matched on an off-by-one digit basis and instead an exact numerical match is required. This reduces the number of found pendant sums from 2422 (allowing off by one errors) to 2210. Once again, we are trading off recall for precision.

Additional culling is done. When a subsidiary can match more than one set of summands, the shortest one is chosen. When a subsidiary is under 100, and is a multiple of 10, it is not chosen.

3. Significance Criteria:

Probably we should set significance based on cord value. For now, we simply look at the number of subsidiary cords that sum…

4. Summary Results:

Measure Result
Number of Khipus That Match 117 (18%)
Number of Significant Khipus 45 (7%)
Five most Significant Khipu UR113, AS069, QU012, UR1166, UR1175
Image Quilt Click here
Database View Click here

5. Summary Charts:

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_ascher_subsidiary_pendant_sum import FieldmarkSubsidiaryPendantSum
aFieldmark = FieldmarkSubsidiaryPendantSum()
fieldmark_dataframe = aFieldmark.dataframes[0].dataframe
raw_match_dict = aFieldmark.raw_match_dict()
Code
# Plot Matching khipu
import plotly.express as px
import pandas as pd

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 Subsidiary Pendant Sum Cords", }, 
            title=f"Matching Khipu ({len(matching_khipus)}) for Number of Subsidiary Pendant 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 Subsidiary Pendant Sum Cords", },
             title=f"Significant Khipu ({len(significant_khipus)}) for Number of Subsidiary Pendant Sum Cords", width=944, height=450).update_layout(showlegend=True).show()
Code
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 Subsidiary Pendant Sum Cords", },
             title=f"Significant Khipu ({len(significant_khipus)}) for Number of Subsidiary Pendant Sum Cords", width=944, height=450).update_layout(showlegend=True).show()

6. Exploratory Data Analysis:

6.1 Handedness

Code
import utils_khipu as ukhipu
import qollqa_chuspa as qc
pd.options.mode.chained_assignment = None  # default='warn'

sum_cord_df = aFieldmark.dataframes[1].dataframe
sum_cord_df['group_index'] = [ukhipu.group_index_from_cord_rep(aString) for aString in sum_cord_df.cord_index.values]

right_sum_cord_index_df = sum_cord_df[sum_cord_df.handedness >= 0]
left_sum_cord_index_df = sum_cord_df[sum_cord_df.handedness < 0]
# This is a special case correction for subsidiary
right_sum_cord_index_df['cord_index'] = [ukhipu.cord_index_from_cord_rep(aString) for aString in right_sum_cord_index_df.cord_index.values]
left_sum_cord_index_df['cord_index'] = [ukhipu.cord_index_from_cord_rep(aString) for aString in left_sum_cord_index_df.cord_index.values]

num_right_sums = len(right_sum_cord_index_df) 
num_left_sums = len(left_sum_cord_index_df) 
num_sums = num_right_sums + num_left_sums

right_pct = round(100.0*float(num_right_sums)/float(num_sums))
left_pct = round(100.0*float(num_left_sums)/float(num_sums))
print(f"Out of {num_sums} subsidiary pendant sums, there are {num_right_sums}({right_pct}%) right_handed sums and {num_left_sums}({left_pct}%) left-handed sums")
Out of 873 subsidiary pendant sums, there are 505(58%) right_handed sums and 368(42%) left-handed sums
Code
def handed_color(x): return 0.0 if x < 0 else 1.0
sum_cord_df['handed_color'] = [handed_color(x) for x in sum_cord_df.handedness.values]

fig = (px.scatter(sum_cord_df, x="num_summands", y="cord_value", log_y=True,
                 color='handed_color', color_continuous_scale=['#3c3fff', '#ff3030',],
                 size="num_summands",
                 labels={"num_summands": "Number of contiguous cords in sum", "cord_value": "Sum Cord Value (Log(y) scale)"},
                 hover_data=['kfg_name', 'cord_value', 'num_summands', 'handedness'], title="<b>Subsidiary Pendant Sums</b> - <i>Red is Right-Handed: Hover Over Circles to View Khipu/Cord Info</i>",
                 width=944, height=944)
        .update_layout(showlegend=False).update(layout_coloraxis_showscale=False).show()
      )
Code
from statistics import mean

handedness_mean = mean(list(sum_cord_df.handedness.values))

fig = (px.scatter(sum_cord_df, x="handedness", y="cord_value", log_y=True,
                 size="num_summands", 
                 color='handed_color', color_continuous_scale=['#3c3fff', '#ff3030',],
                 labels={"handedness": f"Handedness - Mean({handedness_mean})", "cord_value": "Cord Value (Log(y) scale)", },
                 hover_data=['kfg_name'], title="<b>Handedness of Subsidiary Pendant Sums</b> - <i>Red is Right-Handed: Hover Over Circles to View Khipu/Cord Info</i>",
                 width=944, height=944)
        .add_vline(x=handedness_mean)
        .update_layout(showlegend=False).update(layout_coloraxis_showscale=False).show()
      )

Let’s try that again without AS069:

Code
not_as069_df = sum_cord_df[sum_cord_df.kfg_name != "AS069"]
fig = (px.scatter(not_as069_df, x="handedness", y="cord_value", log_y=True,
                 size="num_summands", 
                 color='handed_color', color_continuous_scale=['#3c3fff', '#ff3030',],
                 labels={"handedness": f"Handedness - Mean({handedness_mean})", "cord_value": "Cord Value (Log(y) scale)", },
                 hover_data=['kfg_name'], title="<b>Handedness of Subsidiary Pendant Sums Without AS069</b> - <i>Red is Right-Handed: Hover Over Circles to View Khipu/Cord Info</i>",
                 width=944, height=944)
        .add_vline(x=handedness_mean)
        .update_layout(showlegend=False).update(layout_coloraxis_showscale=False).show()
      )

Once again, we see an even distribution of sums both the left and to the right.

Code
import plotly.graph_objects as go
import plotly.express as px
import numpy as np

def make_heatmap_array(aSumDF, 
                       title="Sum Count occurrence", 
                       max_num_groups=None, 
                       max_num_cords=None,
                       is_right_handed = True):
    group_indexes = aSumDF.group_index.values
    cord_indexes = aSumDF.cord_index.values
    num_groups = 1+max(aSumDF.group_index.values)
    num_cords = 1+max(aSumDF.cord_index.values)
    if max_num_groups is None: max_num_groups = num_groups 
    if max_num_cords is None: max_num_cords = num_cords 
    position_count_array = np.zeros(shape=(max_num_cords, max_num_groups), dtype=np.int64) 
    for index in range(len(aSumDF)):
        group_pos = group_indexes[index]
        cord_pos = cord_indexes[index]
        if group_pos < max_num_groups and cord_pos < max_num_cords:
            position_count_array[cord_pos, group_pos] += 1

    if not is_right_handed: position_count_array = position_count_array[:,1:max_num_groups]
    dir_x = 1 if is_right_handed else -1
    data = position_count_array.tolist()
    
    fig = (go.Figure(data=go.Heatmap(z=data, dx=dir_x, dy=-1))
                     .update_layout(width=944, height=944, title=title)
                     .show())
Code
make_heatmap_array(right_sum_cord_index_df, max_num_groups=30, max_num_cords=50, 
                   title="<b>Top occurrences</b> of RIGHT-HANDED</b> Subsidiary Pendant Sum Positions (O Based Indexing)",
                   is_right_handed=True)
make_heatmap_array(left_sum_cord_index_df, max_num_groups=30, max_num_cords=50, 
                   title="<b>Top occurrences</b> of <b>LEFT-HANDED</b> Subsidiary Pendant Sum Positions (from the right - O Based Indexing)",
                   is_right_handed=False)

6.2 Sum and Summand Cords

Code
fig = (px.scatter(sum_cord_df, y="cord_value", x="num_summands", log_y=True,
                  #size="cord_value", 
                  color="cord_value", 
                  labels={"num_summands":"# Summands", "group_index":"Cluster Index (0 based)", "cord_index": "Cord Index (0 Based)", "cord_value": "Cord Value"},
                  hover_name='kfg_name', hover_data=['group_index', 'cord_index', 'cord_value', 'num_summands'], 
                  title="<b>Subsidiary Pendant Sums:</b> Cord Value vs Number Summands - <i>Hover Over Circles to View Khipu/Cord Info</i>",
                  width=944, height=944)
        .update_layout(showlegend=True)
        .show()
      )

7. Conclusion

We could cut and paste the conclusions from indexed pendant sums, and it would almost be correct :-)

  • Once again, we see the classic 60/40 split between right and left-handed sums, although here it is 57% to 43%. This consistent 60/40 split across all four sum relations is astonishing.
  • Dropping AS069 gives us more insight into the distance between sums and summands. It’s not that large. When the outlier AS069 is dropped, the distance between summands and sum cord follows a roughly triangular distribution - Large sum cord values are close to their summands, and small sum cord values can be farther away (1-30 cords) from their summands.
  • Subsidiary pendant sums follow the same overall location distribution that pendant-pendant sums, pendant-color sums, and color pendant sums follow.