Percent of S and Z Knots


1. Percent of S and Z Knots

The percent of S knots vs Z knots in a khipu. Various theories about S and Z knots encoding moiety information or some other form of binary information exist in the literature. If you have not studied the distribution of recto and verso cord attachment, known to encode moiety information, please do so now.

Let’s explore a similar type of arrangement for S-knots vs Z-knots.

2. Summary Charts:

Summary Results:

Measure Result
Number of Khipus with S Knots 310 (48%)
Number of Khipus with Z Knots 387 (60%)
Five most Significant Khipu KH0081, KH0049, HP039, UR052, UR039
Image Quilt Click here

Z Knot vs. S Knot Plots

Code
# Initialize plotly
import plotly
plotly.offline.init_notebook_mode(connected = False);

# Read in the Fieldmark and its associated dataframe and match dictionary
import qollqa_chuspa as qc
from fieldmark_khipu_summary import FieldmarkPercentSKnots, FieldmarkPercentZKnots
aFieldmark = FieldmarkPercentSKnots()
matching_khipus =   aFieldmark.matching_khipus() 

(khipu_dict, all_khipus) = qc.fetch_khipus()
khipu_summary_df = aFieldmark.get_khipu_summary_df()
khipu_summary_df['percent_s_knots'] = [round(100.0*x, 1) for x in list(khipu_summary_df.percent_s_knots.values)]
khipu_summary_df['percent_z_knots'] = [round(100.0*x, 1) for x in list(khipu_summary_df.percent_z_knots.values)]
khipu_summary_df.sort_values(by=['percent_s_knots'], inplace=True, ascending=False)

khipu_names = list(khipu_summary_df.kfg_name.values)
percent_s_knots = list(khipu_summary_df.percent_s_knots.values)
percent_z_knots = list(khipu_summary_df.percent_z_knots.values)
Code
from plotly import graph_objects as go
fig = (go.Figure()
         .add_trace(go.Bar(x=khipu_names,
                           y=percent_s_knots,
                           name='Percent S Knots',
                           marker_color='red'
                           ))
       .add_trace(go.Bar(x=khipu_names,
                         y=percent_z_knots,
                         name='Percent Z Knots',
                         marker_color='green'
                            ))
       .update_layout(barmode='group', width=1500, height=450, xaxis_tickangle=-90).show()
      )
Code
from plotly import express as px    

fig = px.scatter(khipu_summary_df, 
                 x='percent_z_knots', y='percent_s_knots', 
                 hover_name='kfg_name', hover_data=["percent_s_knots","percent_z_knots", "num_knots", "num_cords"],
                 size="num_cords", 
                 color='num_knots', 
                 labels={"percent_s_knots":"% S Knots", 'percent_z_knots':"% Z Knots"},
                 title=f"<b>% S-Knots/Z-Knots</b> - Size: # Cords, Color: Knot Count - Hover over circles for more information", 
                 width=944, height=944).update_layout(showlegend=True).show()

Clearly S and Z knots exhibit the same kind of marking/pairing that Verso and Recto attachments do!

3. Exploratory Data Analysis:

3.1 S-Knots on one side, Z-knots on the other…

In the history of decipherment, after numbers are decoded, the next semantic concept to be deciphered is usually dates.

Articles by Gary Urton, Alberto Saez-Rodríguez, and Juliana Martins have all proposed “calendar” khipus to describe an astronomical accounting of some “thing”. Saez-Rodriquez, for example, ties UR1104 to the Pleiades. Another khipu, UR1084 is also frequently mentioned in the literature.

These “astronomical” khipus use a spatial arrangement of S and Z knots in spatial quilt-like “blocks” (i.e. tokapu). Can we identify other khipus like this? Let’s use a simple hack, and look at the mean position of S knots and Z knots as a guide to searching out these kinds of khipus.

Code
# First cull database to khipus with at least a 70/30 or 30/70 mix of s and z knots
cutoff_threshold = 30.0
at_least_30_pct_s_df = khipu_summary_df[khipu_summary_df.percent_s_knots >= cutoff_threshold]
at_least_30_pct_s_and_z_knots_df = at_least_30_pct_s_df[at_least_30_pct_s_df.percent_z_knots >= cutoff_threshold]
fig = px.scatter(at_least_30_pct_s_and_z_knots_df, 
                 x='percent_z_knots', y='percent_s_knots', 
                 hover_name='kfg_name', hover_data=["percent_s_knots","percent_z_knots", "num_knots", "num_cords"],
                 size="num_cords", 
                 color='num_knots', 
                 labels={"percent_s_knots":"% S Knots", 'percent_z_knots':"% Z Knots"},
                 title=f"<b>Percent of Minimum of 30% S-Knots and Z-Knots</b> - Size is # Cords, Color is Knot Count", 
                 width=944, height=944).update_layout(showlegend=True).show()
len(at_least_30_pct_s_and_z_knots_df)
263
Code
from statistics import mean

search_khipus = list(at_least_30_pct_s_and_z_knots_df.kfg_name.values)
def knot_moment(knots):
    knot_cords = [aKnot.cord for aKnot in knots if not aKnot.cord.is_subsidiary_cord()]
    cord_indices = [aCord.pendant_index() for aCord in knot_cords]
    return round(mean(cord_indices),1) if len(cord_indices) > 0 else 0

calendar_candidates = []
for khipu_name in sorted(search_khipus):
    aKhipu = khipu_dict[khipu_name]
    s_moment = knot_moment(aKhipu.s_knots())
    z_moment = knot_moment(aKhipu.z_knots())
    mid_moment = aKhipu.num_pendant_cords()/2
    delta_s = abs(s_moment-mid_moment)
    delta_z = abs(z_moment-mid_moment)
    if  (delta_s > 2) and (delta_z > 2):
        if (s_moment < mid_moment) and (mid_moment < z_moment):
            calendar_candidates.append((khipu_name, s_moment, mid_moment, z_moment))
        if (z_moment < mid_moment) and (mid_moment < s_moment):
            calendar_candidates.append((khipu_name, s_moment, mid_moment, z_moment))

calendar_candidates_names = [x[0] for x in calendar_candidates]
calendar_candidates
[('AS074', 3.5, 46.5, 53.4),
 ('JC009', 42.5, 38.0, 35.7),
 ('JC010', 121, 80.0, 76.2),
 ('JC023', 21, 16.5, 12.7),
 ('KH0350', 19.5, 14.5, 12),
 ('QU011', 71.7, 59.0, 30.7),
 ('QU012', 76.9, 79.5, 83.0),
 ('UR017', 8.2, 48.5, 53.1),
 ('UR034', 50.5, 43.5, 40.9),
 ('UR050', 90.4, 82.0, 75.9),
 ('UR053C', 41.4, 30.5, 26.9),
 ('UR061', 18, 20.5, 23.3),
 ('UR063', 37.9, 55.5, 59.8),
 ('UR072', 46.5, 30.5, 27.2),
 ('UR087', 201, 153.0, 146.0),
 ('UR090', 26, 54.0, 57.0),
 ('UR1032', 344, 207.5, 175.3),
 ('UR1033F', 2, 10.5, 13.1),
 ('UR1114', 123.9, 169.5, 184.0),
 ('UR198', 143.9, 128.5, 120.1),
 ('UR269', 152.9, 144.5, 136.7),
 ('UR270', 108.4, 81.0, 77.0),
 ('UR274A', 53, 46.5, 43.2),
 ('UR274B', 53.5, 50.5, 46.7),
 ('UR283', 77.5, 91.5, 103.7),
 ('UR288', 9.5, 14.5, 18.8),
 ('UR292A', 36.2, 26.5, 24.4)]

Potential Calendar Khipus:

QU011 QU019 UR063
UR074 KH0033 UR1052
UR1084 UR1104 UR113
UR166 UR195 UR198
UR199 UR230 UR283
UR292A
Code
candidate_mask = [name in calendar_candidates_names for name in list(khipu_summary_df.kfg_name.values)]
calendar_candidate_df = khipu_summary_df[candidate_mask]
fig = px.scatter(calendar_candidate_df, 
                 x='percent_z_knots', y='percent_s_knots', 
                 hover_name='kfg_name', hover_data=["percent_s_knots","percent_z_knots", "num_knots", "num_cords"],
                 size="num_cords", 
                 color='num_knots', 
                 labels={"percent_s_knots":"% S Knots", 'percent_z_knots':"% Z Knots"},
                 title=f"<b>Potential Calendar Khipu Candidates</b> - Size is # Cords, Color is Knot Count - Hover over circles for more information", 
                 width=944, height=944).update_layout(showlegend=True).show()

UR1052, UR1084, UR1104 and UR113 all appear to be good candidates for a calendar khipu. Let’s take a quick look at them spatially:

Code
import math

def plot_s_z_knot_map(aKhipuName):
    def num_digits(aCluster):
        """ Returns 1 + the magnitude base 10 of knotted value """
        cluster_value = sum([aKnot.value for aKnot in aCluster.knots()])
        return 1 + int(math.floor(math.log10(cluster_value))) if cluster_value > 0 else 0

    def cord_state(col, row):
        theCord = thePendantCords[col]
        knots = theCord.all_knots(include_subsidiaries=False)
        the_state = 0
        if row < len(knots):
            row_knot = knots[row]
            if   row_knot.is_S_knot(): the_state =  1
            elif row_knot.is_Z_knot(): the_state = -1

        return (the_state)
    
    aKhipu = khipu_dict[aKhipuName]
    thePendantCords = aKhipu.pendant_cords()
    num_cols = len(thePendantCords)
    num_rows = max([aCord.num_knots() for aCord in thePendantCords])
    
    knot_states = [[cord_state(col_index, row_index)  for col_index in range(num_cols)] for row_index in range(num_rows,0,-1)]
    fig = go.Figure(data=go.Heatmap(z=knot_states, colorscale='Cividis_r', showlegend=False))
    fig.update_layout(showlegend=False, width=944, title=f"<b>{aKhipuName}</b> - Spatial Map of S vs Z Knots").show()
     
plot_s_z_knot_map('UR1104')
plot_s_z_knot_map('UR1084')
plot_s_z_knot_map('UR113')

3.2 S/Z Knots vs Banded/Seriated Khipus

Code
# Cull database to khipus with at least a 95/5 or 5/95 mix of s and z knots
cutoff_threshold = 5.0
at_least_5_pct_s_df = khipu_summary_df[khipu_summary_df.percent_s_knots >= cutoff_threshold]
at_least_5_pct_s_and_z_knots_df = at_least_5_pct_s_df[at_least_5_pct_s_df.percent_z_knots >= cutoff_threshold]

def banded_color(kfg_name): 
    aKhipu = khipu_dict[kfg_name]
    is_banded = aKhipu.num_banded_groups() > khipu_dict[kfg_name].num_seriated_groups()
    return 0.0 if is_banded else 1.0
def banded_ratio(kfg_name):
    aKhipu = khipu_dict[kfg_name]
    total_groups = aKhipu.num_cord_groups()
    return aKhipu.num_banded_groups()/total_groups if total_groups > 0 else 0

at_least_5_pct_s_and_z_knots_df['banded_color'] = [banded_color(x) for x in at_least_5_pct_s_and_z_knots_df.kfg_name.values]
at_least_5_pct_s_and_z_knots_df['banded_ratio'] = [banded_ratio(x) for x in at_least_5_pct_s_and_z_knots_df.kfg_name.values]

fig = px.scatter(at_least_5_pct_s_and_z_knots_df, 
                 x='num_seriated_groups', y='num_banded_groups', 
                 hover_name='kfg_name', hover_data=["num_banded_groups", "num_seriated_groups", "percent_s_knots","percent_z_knots", "num_knots", "num_cords"],
                 size="percent_s_knots", 
                 color='banded_color', color_continuous_scale=['#3c3fff', '#ff3030',],
                 labels={"percent_s_knots":"% S Knots", 'percent_z_knots':"% Z Knots"},
                 title=f"<b>S-Knots and Z-Knots by Banded/Seriated</b> - Blue=Banded, Red=Seriated, Size=%S Knots", 
                 width=944, height=944).update_coloraxes(showscale=False).show()

This chart shows the use of mixed S-Z knots is a pattern that occurs in both banded and seriated khipus.

4. Conclusions:

Clearly S and Z knots have a relationship to each other as a form of marking equivalent to Verso and Recto cords. However, studies by other khipu scholars indicate that S and Z knots appear to have a spatial significance in terms of their position on the khipu.

A simple test to find khipus that maybe astronomical in nature exists - use the spatial separation of S and Z knots as an indicator. Doing so, indicates that, at a minimum, UR1052, UR1084, UR1104 and UR113 appear to be good candidates for a calendar khipu.