Group Sum Bands


1. Number of Group Sum Bands

Number of groups whose (loosely) left half sum equals the (loosely) right half sum.

Marcia Ascher notes that in AS066 There are a number of groups whose (loosely) left half sum equals the (loosely) right half sum, which in turn equals half of a top cord (the Double Sum Top Cord fieldmark). She notes it as:

j=14Pij=j=58Pij=top_value2for(i=13,15,16,17,18,21,22,23,24)

This half summing where the left half sum equals the right half sum is a fieldmark worth reviewing.

2. Search Criteria:

It is simple to see if there is some split index exists where the left sum equals the right sum. The trick here is to remove the uninteresting false positives, of which there are many:

  • Groups whose total sum is less than 5 are removed.
  • Groups which only have one value repeated are removed.
  • Groups where the left or right sum band consist of one cord are removed. These are really pendant-pendant sum relationships.

This leaves 64 interesting khipus sums with 114 group’s which are sum bands.

3. Summary Results:

Measure Result
Number of Khipus That Match 87 (13%)
Number of Significant Khipus 87 (13%)
Five most Significant Khipu UR037, AS069, UR004, AS066, UR060
XRay Image Quilt Khipu by Sum Relationships
Database View Click here

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

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 Group Sum Bands", }, 
            title=f"Matching Khipu ({len(matching_khipus)}) for Number of Group Sum Bands",  width=944, height=450).update_layout(showlegend=True).show()
UR037UR004UR060JC012UR021UR211UR1032UR1109MM001QU019UR058AUR072UR114UR152UR170UR195UR242UR272UR1034AS013AS026AAS093AS164AS189AS198AS207AHP039HP057JC009JC015KH0058MM021QU013UR042UR052UR127UR186UR208UR219UR232UR245UR254UR266UR271UR284UR1049UR1091UR11510123456
Matching Khipu (96) for Number of Group Sum BandsKhipu NameNumber of Group Sum Bands
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 Group Sum Bands", },
             title=f"Significant Khipu ({len(significant_khipus)}) for Number of Group Sum Bands", width=944, height=450).update_layout(showlegend=True).show()
UR037UR004UR060JC012UR021UR211UR1032UR1109MM001QU019UR058AUR072UR114UR152UR170UR195UR242UR272UR1034AS013AS026AAS093AS164AS189AS198AS207AHP039HP057JC009JC015KH0058MM021QU013UR042UR052UR127UR186UR208UR219UR232UR245UR254UR266UR271UR284UR1049UR1091UR11510123456
Significant Khipu (96) for Number of Group Sum BandsKhipu NameNumber of Group Sum Bands

5. Exploratory Data Analysis

Code
# Khipu Imports
import qollqa_chuspa as qc  # A Khipu Maker is known (in Quechua) as a Khipu Kamayuq
import math
# Load khipus
(khipu_dict, all_khipus) = qc.fetch_khipus()

sum_group_df = aFieldmark.dataframes[1].dataframe
sum_group_df['log_num_cords'] = [math.log(num_cords) for num_cords in sum_group_df.num_cords.values]

There are 114 group sum matches in the entire Khipu Field Guide. First, let’s review there value (how much they sum to) and where the group sum band splits.

Code
fig = (px.scatter(sum_group_df, x="split_ratio", y="group_sum", log_y=True,
                 size="num_cords", color="log_num_cords", 
                 labels={"split_ratio": "Where the group is halved - .01 is to the left, .99 is to the right", "group_sum": "group_sum (Log(y) scale)"},
                 hover_data=['kfg_name', 'num_cords'], title="<b>Sum Paired Groups</b> - <i>Hover Over Circles to View Khipu/Cord Info</i>",
                 width=944, height=944)
        .update_layout(showlegend=True)
        .show()
      )
0.10.20.30.40.50.60.70.8510251002510002510k
1.522.533.54log_num_cordsSum Paired Groups - Hover Over Circles to View Khipu/Cord InfoWhere the group is halved - .01 is to the left, .99 is to the rightgroup_sum (Log(y) scale)
Code
fig = (px.histogram(sum_group_df, x="split_ratio", nbins=100,
                 width=944, height=500, title="<b>Sum Paired Group occurrences</b> <i>by</i> <b>Split Ratio</b>",)
        .update_layout()
        .show()
      )
0.10.20.30.40.50.60.705101520253035
Sum Paired Group occurrences by Split Ratiosplit_ratiocount

Investigation of each sum pair is interesting. There is of course, a density at the split at .5 - right down the middle of the group. Additionally, there is a density at .33 - a third of a group. Lesser densities are evident at 0.4(2/5), 0.6(3/5) and at .333(2/6) and .666(4/6). Hovering over the scatter plot, the data shows that the values are significant, involving both large numbers of cords, and cords with large values.

5.1 Review by Banded vs. Seriated

Code
sum_group_df.head()
Unnamed: 0 kfg_name group_index split_index split_ratio num_cords split_sum group_sum log_num_cords
0 0 AS008 16 2 0.500 4 18 36 1.386294
1 1 AS013 4 4 0.500 8 18 36 2.079442
2 2 AS023 0 2 0.250 8 20 40 2.079442
3 3 AS026A 1 4 0.211 19 15 30 2.944439
4 4 AS066 36 2 0.500 4 25 50 1.386294
Code
def fetch_group(khipu_name, group_index): 
    return khipu_dict[khipu_name][int(group_index)]

group_tuples = list(zip(list(sum_group_df.kfg_name.values), list(sum_group_df.group_index.values)))
groups = [fetch_group(kfg_name, group_index) for (kfg_name, group_index) in group_tuples]
banded_groups = [aGroup for aGroup in groups if aGroup.is_banded_group()]
seriated_groups = [aGroup for aGroup in groups if not aGroup.is_banded_group()]
print(f"Out of {len(groups)} groups, there are {len(banded_groups)} banded groups and {len(seriated_groups)} seriated_groups")
Out of 157 groups, there are 37 banded groups and 120 seriated_groups

5.2 Review by Color

5.2.1 Banded Groups

Code
import utils_loom as uloom
import re
from collections import Counter

color_strings = [[aCord.main_color() for aCord in aGroup[:]] for aGroup in banded_groups]
color_strings = uloom.flatten_list(color_strings)
def is_complex_color(anAscherColor): return (len(re.split('[\:\-\%]',anAscherColor)) > 1)
basic_colors = [color for color in color_strings if not is_complex_color(color)]
complex_colors = [color for color in color_strings if is_complex_color(color)]
basic_counter = Counter(basic_colors)
complex_counter = Counter(complex_colors)

print(f"There are {len(set(basic_colors))} basic colors over {len(basic_colors)} cords, and {len(set(complex_colors))} complex colors over {len(complex_colors)} cords.")
print(f"20 Most Common Basic Colors: {basic_counter.most_common(20)}")
print(f"20 Most Common Complex Colors: {complex_counter.most_common(20)}")
There are 11 basic colors over 220 cords, and 2 complex colors over 31 cords.
20 Most Common Basic Colors: [('W', 88), ('AB', 30), ('MB', 27), ('B', 25), ('PR', 10), ('YB', 10), ('LB', 10), ('HB', 7), ('KB', 5), ('LG', 4), ('LK', 4)]
20 Most Common Complex Colors: [('W:MB', 22), ('LG:AB', 9)]

5.2.2 Seriated Groups

Code
color_strings = [[aCord.main_color() for aCord in aGroup[:]] for aGroup in seriated_groups]
color_strings = uloom.flatten_list(color_strings)
from collections import Counter
def is_complex_color(anAscherColor): return (len(re.split('[\:\-\%]',anAscherColor)) > 1)
basic_colors = [color for color in color_strings if not is_complex_color(color)]
complex_colors = [color for color in color_strings if is_complex_color(color)]
basic_counter = Counter(basic_colors)
complex_counter = Counter(complex_colors)

print(f"There are {len(set(basic_colors))} basic colors over {len(basic_colors)} cords, and {len(set(complex_colors))} complex colors over {len(complex_colors)} cords.")
print(f"20 Most Common Basic Colors: \n{basic_counter.most_common()}")
print(f"20 Most Common Complex Colors: {complex_counter.most_common()}")
There are 39 basic colors over 959 cords, and 67 complex colors over 284 cords.
20 Most Common Basic Colors: 
[('MB', 215), ('AB', 186), ('W', 180), ('YB', 105), ('B', 62), ('KB', 45), ('LB', 22), ('HB', 20), ('RB', 13), ('BS', 12), ('PB', 12), ('GG', 12), ('RL', 6), ('YG', 6), ('DG', 6), ('BG', 5), ('G0', 5), ('NB', 5), ('DB', 4), ('BB', 4), ('0G', 3), ('R', 3), ('PR', 3), ('G', 2), ('GB', 2), ('LG', 2), ('MG', 2), ('PG', 2), ('LK', 2), ('VR', 2), ('VG', 2), ('RG', 2), ('GY', 1), ('EB', 1), ('FR', 1), ('KG', 1), ('LC', 1), ('RD', 1), ('PK', 1)]
20 Most Common Complex Colors: [('W:KB', 34), ('W:AB', 32), ('W-AB', 18), ('AB:MB', 16), ('W:MB', 15), ('GG:MB', 14), ('MB:KB', 12), ('W-KB', 11), ('BB:KB', 10), ('YB:MB', 8), ('W:NB', 7), ('AB:GG', 6), ('AB:KB', 5), ('W:FB', 4), ('AB:RB', 4), ('YB:PB', 4), ('W:GG', 4), ('W-MB', 4), ('W%MB', 3), ('AB:FR', 3), ('W:YB', 3), ('MB:CB', 3), ('YG-DB', 3), ('0G:DG', 2), ('B:CB', 2), ('W:B', 2), ('MB-HB', 2), ('AB-MB', 2), ('BY:MB', 2), ('GB-HB', 2), ('RL:AB', 2), ('YB:PG', 2), ('YB:AB', 2), ('AB:G', 2), ('MG:RB', 2), ('W:AB:KB', 2), ('W:GG:MB', 2), ('W-BG', 2), ('W:DB', 2), ('DB-CB', 2), ('W%AB', 1), ('LG:RL', 1), ('HB:KB', 1), ('LG-BY', 1), ('YB-KB', 1), ('PK-R', 1), ('YB:KB', 1), ('YB:LK', 1), ('W:BL', 1), ('W:LG', 1), ('W:LG:AB', 1), ('LG-AB:W', 1), ('W-LA', 1), ('W:GL', 1), ('AB:GG:MB', 1), ('AB:GB:MB', 1), ('MB:HB', 1), ('W:HB', 1), ('W:RL', 1), ('RL-LC', 1), ('BG:MB', 1), ('YB-0Y', 1), ('W:W:GG', 1), ('W:GL:KB', 1), ('GL:MB', 1), ('AB:NB', 1), ('YG:DB', 1)]

5.3 Review by Handedness

Code
%%capture --no-display 

def khipu_moment(aKhipu):
    """ If the moment-weight is '1', then the moment-arm is simply the average of the position indices """
    khipu_df = sum_group_df[sum_group_df.kfg_name == aKhipu.name()]
    sum_band_groups = [aKhipu[int(group_Pos)] for group_Pos in khipu_df.group_index.values]
    mid_point = aKhipu.num_cord_groups()/2.0
    moment_pt = mean([aGroup.position()+1 for aGroup in sum_band_groups]) if len(sum_band_groups) > 0 else mid_point
    #moment_pt = sum([aGroup.position()+1 for aGroup in aKhipu.cord_groups() if aGroup.is_roughly_linearly_decreasing()])/aKhipu.num_linear_decreasing_groups()
    return moment_pt - mid_point

def khipu_mean_group_sum(aKhipu):
    """ If the moment-weight is '1', then the moment-arm is simply the average of the position indices """
    khipu_df = sum_group_df[sum_group_df.kfg_name == aKhipu.kfg_name()]
    sum_band_groups = [aKhipu[int(group_Pos)] for group_Pos in khipu_df.group_index.values]
    mean_sum = mean([aGroup.group_cord_sum()/2 for aGroup in sum_band_groups]) if len(sum_band_groups) > 0 else 0
    return mean_sum

def khipu_sum_bands(aKhipu):
    khipu_df = sum_group_df[sum_group_df.kfg_name == aKhipu.kfg_name()]
    return len(khipu_df)

group_sum_khipus = [aKhipu for aKhipu in all_khipus if aKhipu.kfg_name() in matching_khipus]
group_sum_moments = [khipu_moment(aKhipu) for aKhipu in group_sum_khipus]

mean_moment_rep = mean(group_sum_moments)
mean_moment_rep_text = f'{mean_moment_rep:2.1f}'
print(f"Average moment arm is: {mean_moment_rep}")    

csb_df = pd.DataFrame(columns = ['khipu_id', 'name', 'num_groups', 'mean_group_sum', 'num_sum_bands', 'moment']) 
for i, aKhipu in enumerate(group_sum_khipus):
    record = {'kfg_name': aKhipu.kfg_name(), 
              'num_groups': aKhipu.num_cord_groups(),
              'mean_group_sum':khipu_mean_group_sum(aKhipu),
              'num_sum_bands': khipu_sum_bands(aKhipu),
              'moment': khipu_moment(aKhipu), 
              }
    csb_df = pd.concat([csb_df, pd.DataFrame([record])], ignore_index=True)   

fig = (px.scatter(csb_df, x="moment", y="num_groups", 
        size=list(csb_df.num_sum_bands.values),
        color=list(csb_df.mean_group_sum.values),
        labels={"moment": "Khipu Moment Arm", "num_groups": "Number of Overall Groups in Khipu"},
        hover_data=['kfg_name'], 
        title="<b>Khipus with Group Sum Bands</b> - Marker Size is Number of Group Sum Bands",
        width=944, height=944)\
.add_shape(type="line", x0=mean_moment_rep, y0=0, x1=mean_moment_rep, y1=140)\
.add_annotation(x=8, y=150,
                text=f"Mean moment arm is ~{mean_moment_rep_text} (Vertical Line)", showarrow=False)\
.update_layout(showlegend=True).show())
−80−60−40−200200100200300400
500100015002000250030003500colorKhipus with Group Sum Bands - Marker Size is Number of Group Sum BandsKhipu Moment ArmNumber of Overall Groups in KhipuMean moment arm is ~0.7 (Vertical Line)

6. Conclusion

  • Group sum bands split mostly down the middle, and occasionally at 1/3 and 2/3 of the way through the group.
  • Of those groups that are group sum bands, group sum bands occur on seriated groups twice as often as banded groups (2:1) (compared to the overall KFG prior distribution of 4 seriated groups for every 3 banded groups (1.33:1).
  • Large group sum bands occur mostly on the right.
  • It is not necessary for the group sum bands to have equal numbers of cords, or to be multiples of 2 for each side.
  • It is not evident when a group sum band occurs, other than by doing the arithmetic. It seems this is a convenient way of pre-organizing something. Sometimes a top cord occurs. Sometimes the cord attachment is different. But overall, there appears to be little in the way of a signifier.