Figure-Eight-Knots and Ascher Summands


1.0 Building a Database of Ascher Summands with Figure 8 Knots

Aware that there is a potential relationship between Figure-8-knots and Ascher Sums, it’s time to build dataframes of potential correspondences. Two dataframes are created:

  1. A Sums-and-Figure-8-Knots Dataframe showing Sum Information for all pendant ascher sums, augmented with information about figure-eight-knot cords in the summand ranges.
  2. A Left-Handed and Right Handed Eight-Knot-Sum-Summary “contingency table” of sorts, which summarizes the above table by khipu.

2.0 Building the Augmented Sums-and-Eight-Knot Dataframe

2.1 Fields of the Augmented Sums-and-Eight-Knot Dataframe

First we build a database of sum/summands by concatenating the data frames for pendant-pendant-sums, colored-pendant-sums, and indexed-pendant sums, ignoring other Ascher sums due to the paucity of occurence. This database, known in the code below as the pendant_ascher_sums_df consists of the following information:

KFG Name/Sum Type

  • kfg_name - The Khipu Field Guide Name
  • sum_type - Is it a ‘pendant-pendant-sum’, ‘colored-pendant-sum’, or ‘indexed-pendant-sum’

Sum Cord

  • cord_name - The Ascher sum cord name
  • cord_index - The sum cord’s group/cord index
  • cord_value - The sum cord’s value
  • cord_color - The Sum Cord’s color

Summands

  • handedness - Is it a left-handed (<0) or right-handed (>= 0) sum?
  • num_summands - How many pendant cord summands in the summand range
  • summand_string - An internal representation for computing use

Figure-8-Knot Information

  • has_figure8knot_indicator - Is there a figure-8-knot “indicator” for the summand range. What is an indicator? For now, an indicator is defined as having a left-edge (the first/left pendant cord in a summand range) that is marked by a figure-8-knot card, or a right-edge (the last/right pendant cord in a summand range) that is marked by a figure-8-knot card. A summand edge cord is marked if it has one or more of the following conditions:

    • The edge cord is an eight-knot cord (ie. a sole-8-knot cord, or a trailing-8-knot cord
    • The edge cord has a subsidiary that is an eight-knot-cord
    • Each edge cord has a left (or right) neighbor, bordering the summand range. i.e. if the summand range is p31 to p37 then p30 is the left neighbor and p38 is the right neighbor. If it has a pendant neighbor, does that neighbor, or it’s subsidiaries have an eight-knot cord.

    For each sum/summand range, these conditions are tagged in the database as:

    • has_left_exact_8knot_cord - a boolean
    • has_right_exact_8knot_cord - a boolean
    • has_left_close_8knot_cord - a boolean
    • has_right_close_8knot_cord - a boolean
    • left_exact_pendant_8knot_cord_name - the pendant cordname if it matches condition
    • right_exact_pendant_8knot_cord_name - the pendant cordname if it matches condition
    • left_exact_subsidiary_8knot_cord_names - the cordnames if they match condition
    • right_exact_subsidiary_8knot_cord_names - the cordname if they match condition
    • left_close_pendant_cord_names - the left neighbor’s pendant cordname if it matches condition
    • right_close_pendant_cord_names - the right neighbor’s pendant cordname if it matches condition
    • left_close_subsidiary_8knot_cord_names - the cordname if they match condition
    • right_close_subsidiary_8knot_cord_names - the cordname if they match condition
  • has_parity_bit

    • Finally there is a parity bit flag. This requires greater explanation and will be discussed in the parity bit section
Code
```{python}
import os
import numpy as np
from scipy import stats
import pandas as pd
from pandas import Series, DataFrame
from collections import Counter

import qollqa_chuspa as qc  # A Khipu Maker is known (in Quechua) as a Khipu Kamayuq
import utils_loom as uloom
import utils_khipu as ukhipu
import utils_pandas as upanda
import utils_kfg_locations as uloc
    
# Plotly
import plotly
from plotly.offline import iplot, init_notebook_mode
import plotly.graph_objects as go
import plotly.express as px
import plotly.figure_factory as ff
plotly.offline.init_notebook_mode(connected = False)
# Load all khipus
(khipu_dict, all_khipus) = qc.fetch_khipus()

KFG_Names = ukhipu.kfg_order(khipu_dict.keys())
khipu_cords_dict = {KFG_Name: khipu_dict[KFG_Name].all_cords(include_bottom_cords=True, include_top_cords=True, include_subsidiaries=True) for KFG_Name in KFG_Names}
total_num_khipu_cords = sum([len(khipu_cords_dict[KFG_Name]) for KFG_Name in KFG_Names])
```

2.2 Building the Augmented Sums-and-Eight-Knot Dataframe

Why only choose pendant-pendant-sums, indexed-pendant-sums and colored-pendant-sums?

It may be useful to recount the number of all pendant ascher sums of the three various types (pendant-pendant-sums, colored-pendant-sums, and indexed-pendant-sums)

Code
```{python}
from fieldmark_ascher_pendant_pendant_sum import FieldmarkPendantPendantSum
from fieldmark_ascher_indexed_pendant_sum import FieldmarkIndexedPendantSum
from fieldmark_ascher_colored_pendant_sum import FieldmarkColoredPendantSum
from fieldmark_ascher_subsidiary_pendant_sum import FieldmarkSubsidiaryPendantSum

pendant_pendant_sums_df = FieldmarkPendantPendantSum().relations_df()
indexed_pendant_sums_df = FieldmarkIndexedPendantSum().relations_df()
colored_pendant_sums_df = FieldmarkColoredPendantSum().relations_df()
subsidiary_pendant_sums_df = FieldmarkSubsidiaryPendantSum().relations_df()

num_pps_khipus = pendant_pendant_sums_df['kfg_name'].nunique() 
num_ips_khipus = indexed_pendant_sums_df['kfg_name'].nunique() 
num_cps_khipus = colored_pendant_sums_df['kfg_name'].nunique() 
num_sps_khipus = subsidiary_pendant_sums_df['kfg_name'].nunique()
print(f"{uloom.percent_info(num_pps_khipus, len(all_khipus))} khipus have pendant_pendant_sums")
print(f"{uloom.percent_info(num_ips_khipus, len(all_khipus))} khipus have indexed_pendant_sums")
print(f"{uloom.percent_info(num_cps_khipus, len(all_khipus))} khipus have colored_pendant_sums")
print(f"{uloom.percent_info(num_sps_khipus, len(all_khipus))} khipus have subsidiary_pendant_sums")
```
58% (411 of 711) khipus have pendant_pendant_sums
29% (204 of 711) khipus have indexed_pendant_sums
39% (278 of 711) khipus have colored_pendant_sums
21% (149 of 711) khipus have subsidiary_pendant_sums

Only the first three sum relations are chosen. We can then concatenate those resulting dataframes together:

Code
```{python}
# Build Concatenated DataFrames for all pendant Ascher Sums
pendant_ascher_sums_df = pd.concat([pendant_pendant_sums_df.copy(deep=True),
                                 indexed_pendant_sums_df.copy(deep=True),
                                 colored_pendant_sums_df.copy(deep=True)])
pendant_ascher_sums_df['sum_type'] = ['pendant_pendant_sum']*len(pendant_pendant_sums_df) + ['indexed_pendant_sum']*len(indexed_pendant_sums_df) + ['colored_pendant_sum']*len(colored_pendant_sums_df)
pendant_ascher_sums_df.fillna('', inplace=True)   
pendant_ascher_sums_df.head()
```
kfg_name cord_name cord_index cord_value cord_color num_summands handedness has_figure8knot_indicator has_left_exact_8knot_cord has_right_exact_8knot_cord ... right_exact_pendant_8knot_cord_name left_exact_subsidiary_8knot_cord_names right_exact_subsidiary_8knot_cord_names left_close_pendant_cord_names right_close_pendant_cord_names left_close_subsidiary_8knot_cord_names right_close_subsidiary_8knot_cord_names has_parity_bit summand_string sum_type
0 CM009 p24 [4, 2] 60 GG 3 -7 True False False ... p15 False GG@[2, 6]:3 + W:KB@[2, 7]:1 + W@[3, 0]:56 pendant_pendant_sum
1 CM009 p25 [4, 3] 17 W:KB 11 -18 True False True ... p12 p1 True GG@[0, 1]:1 + W@[0, 2]:1 + AB@[0, 3]:0 + W:KB@... pendant_pendant_sum
2 CM009 p38 [7, 0] 57 W 2 -21 True True False ... True W:KB@[2, 7]:1 + W@[3, 0]:56 pendant_pendant_sum
3 CM009 p63 [13, 1] 44 AB 7 -8 True True False ... p51 True GG@[10, 2]:1 + W:KB@[10, 3]:0 + W@[11, 0]:6 + ... pendant_pendant_sum
4 CM009 p64 [13, 2] 39 GG 4 -7 True False False ... p55 False GG@[11, 2]:3 + W:KB@[11, 3]:0 + W@[12, 0]:33 +... pendant_pendant_sum

5 rows × 23 columns

Code
```{python}
from pandas2html import Pandas2HTML

file_specs = {
        "output_dir":f"{uloc.fieldmark_staging_dir()}/analyses/database",
        "basename":"figure8knotsums"
        }   
page_specs = {
        "title":"Figure-8 Knot Sums",
        "description":"Khipus whose Ascher Summand Ranges are Marked by Cords W/a Figure-8 Knot",
        }
column_specs = {'cord_value':{'label':"Cord Value", 'formatter':"number"},
                'num_summands':{'label':"# Summands", 'formatter':"number"},
                'handedness':{'label':"Handedness", 'formatter':"number"}}

pendant_ascher_sums_df.fillna('', inplace=True)
Pandas2HTML(file_specs, page_specs, column_specs, from_df=pendant_ascher_sums_df).make_pandas_html(show_html=False);

```
Generated Pandas2HTML HTML table: file:///Users/ashokkhosla/Desktop/LOOM/khipufieldguide/notebook/fieldmarks/analyses/database/figure8knotsums.html

2.3 Viewing the Augmented Sums-and-Eight-Knot Dataframe

The Sums-and-Eight-Knots Dataframe can be viewed here showing Sum Information for all pendant ascher sums, augmented with information about figure-eight-knot cords in the summand ranges.

3.0 Building the Eight-Knot-Sum-Summary

3.1 Fields of the Eight-Knot Sum-Summary Dataframe

Once a detailed, sum-by-sum inventory has been done, a summary contigency table, by khipu can be built. Here called eight_knot_sums_df which has the following “frequency count” information by khipu.

KFG Name Index

  • kfg_name

Sum Information

  • num_ascher_sums
  • num_pps_sums
  • num_cps_sums
  • num_ips_sums

Summary Frequency Counts of Eight-Knot Cord Markings

  • total_8knot_cords - the total number of the khipu’s figure-8-knot cords (including subsidiaries), regardless of what or how they are marked.
  • total_sum8knots - the sum of total_exact_sole8knots + total_close_sole8knots + total_exact_trailing8knots + total_close_sole8knots
  • total_sole8knots - the number of summand-figure-8-knot marker cords that are sole-8-knots
  • total_trailing8knots - the number of summand-figure-8-knot marker cords that are trailing-8-knots
  • total_exact_sole8knots - the number of pendant or subsidiary summand-figure-8-knot marker cords that are sole-8-knots
  • total_exact_trailing8knots - the number of pendant or subsidiary summand-figure-8-knot marker cords that are trailing-8-knots
  • total_close_sole8knots - the number of neigboring pendant or subsidiary summand-figure-8-knot marker cords that are trailing-8-knots
  • total_close_trailing8knots - the number of neigboring pendant or subsidiary summand-figure-8-knot marker cords that are trailing-8-knots

Detailed Frequency Counts of Eight-Knot Cord Markings

  • left_exact_pendant_8knot_cord_type_sole8knot
  • left_exact_pendant_8knot_cord_type_trailing8knot
  • right_exact_pendant_8knot_cord_type_sole8knot
  • right_exact_pendant_8knot_cord_type_trailing8knot
  • left_exact_subsidiary_8knot_cord_type_sole8knot
  • left_exact_subsidiary_8knot_cord_type_trailing8knot
  • right_exact_subsidiary_8knot_cord_type_sole8knot
  • right_exact_subsidiary_8knot_cord_type_trailing8knot
  • left_close_pendant_8knot_cord_type_sole8knot
  • left_close_pendant_8knot_cord_type_trailing8knot
  • right_close_pendant_8knot_cord_type_sole8knot
  • right_close_pendant_8knot_cord_type_trailing8knot
  • left_close_subsidiary_8knot_cord_type_sole8knot
  • left_close_subsidiary_8knot_cord_type_trailing8knot
  • right_close_subsidiary_8knot_cord_type_sole8knot
  • right_close_subsidiary_8knot_cord_type_trailing8knot
Code
```{python}
kfg_names = pendant_ascher_sums_df['kfg_name'].tolist()

def cord_8knot_type(aKFGName, aCordName):
    if len(aCordName) == 0: return ""
    aCord = khipu_dict[aKFGName].find_cord_named(aCordName)
    if aCord is None: return ""
    if aCord.has_sole_eight_knot(): the_8knot_type = "sole_8knot"
    elif aCord.has_trailing_eight_knot(): the_8knot_type = "trailing_8knot"
    else: the_8knot_type = ""
    return the_8knot_type

def eight_knot_cord_types(aColumnName):
    cord_names = pendant_ascher_sums_df[aColumnName].tolist()
    cord_names = [aName.split(',')[0] for aName in cord_names] # Only use first subsidiary
    cord_types = [cord_8knot_type(kfg_names[i], cord_names[i]) for i in range(len(cord_names))]
    return cord_types

pendant_ascher_sums_df['left_exact_pendant_8knot_cord_type'] = eight_knot_cord_types('left_exact_pendant_8knot_cord_name')
pendant_ascher_sums_df['right_exact_pendant_8knot_cord_type'] = eight_knot_cord_types('right_exact_pendant_8knot_cord_name')
pendant_ascher_sums_df['left_exact_subsidiary_8knot_cord_type'] = eight_knot_cord_types('left_exact_subsidiary_8knot_cord_names')
pendant_ascher_sums_df['right_exact_subsidiary_8knot_cord_type'] = eight_knot_cord_types('right_exact_subsidiary_8knot_cord_names')
pendant_ascher_sums_df['left_close_pendant_8knot_cord_type'] = eight_knot_cord_types('left_close_pendant_cord_names')
pendant_ascher_sums_df['right_close_pendant_8knot_cord_type'] = eight_knot_cord_types('right_close_pendant_cord_names')
pendant_ascher_sums_df['left_close_subsidiary_8knot_cord_type'] = eight_knot_cord_types('left_close_subsidiary_8knot_cord_names')
pendant_ascher_sums_df['right_close_subsidiary_8knot_cord_type'] = eight_knot_cord_types('right_close_subsidiary_8knot_cord_names')

pendant_ascher_sums_df.to_csv(f"{uloc.fieldmarks_data_dir()}/figure8knotsums.csv", index=False)   
```
Code
```{python}
from collections import Counter
from fieldmark_figure8knots import FieldmarkFigure8Knots
figure8knots_df = FieldmarkFigure8Knots().relations_df()

eight_knot_cord_type_columns = ['left_exact_pendant_8knot_cord_type', 'right_exact_pendant_8knot_cord_type', 
                                'left_exact_subsidiary_8knot_cord_type', 'right_exact_subsidiary_8knot_cord_type',
                                'left_close_pendant_8knot_cord_type', 'right_close_pendant_8knot_cord_type',
                                'left_close_subsidiary_8knot_cord_type', 'right_close_subsidiary_8knot_cord_type'] 

def calc_num_sole_8knot_cords(aDF, aKFGName, aColumnName):
    kfg_df = aDF[aDF['kfg_name'] == aKFGName]
    sole_8knot_df = kfg_df[kfg_df[aColumnName]=='sole_8knot']
    return sole_8knot_df.shape[0]

def calc_num_trailing_8knot_cords(aDF, aKFGName, aColumnName):
    kfg_df = aDF[aDF['kfg_name'] == aKFGName]
    sole_8knot_df = kfg_df[kfg_df[aColumnName]=='trailing_8knot']
    return sole_8knot_df.shape[0]

def calc_total_8knot_cords(aDF, aKFGName):
    kfg_df = figure8knots_df[figure8knots_df['kfg_name'] == aKFGName]
    return kfg_df.shape[0]

def build_augmented_eight_knot_df(aDF):
    kfg_names = aDF['kfg_name'].tolist()
    eight_knot_types = []
    for kfg_name in ukhipu.lexigraphic_order(uloom.unique(kfg_names)):
        kfg_df = aDF[aDF['kfg_name'] == kfg_name]
        num_sums = kfg_df.shape[0]  
        num_pps_sums = kfg_df[kfg_df['sum_type'] == 'pendant_pendant_sum'].shape[0]
        num_ips_sums = kfg_df[kfg_df['sum_type'] == 'indexed_pendant_sum'].shape[0]
        num_cps_sums = kfg_df[kfg_df['sum_type'] == 'colored_pendant_sum'].shape[0]

        total_exact_sole8knots = (
                calc_num_sole_8knot_cords(aDF, kfg_name, 'left_exact_pendant_8knot_cord_type') + 
                calc_num_sole_8knot_cords(aDF, kfg_name, 'right_exact_pendant_8knot_cord_type') + 
                calc_num_sole_8knot_cords(aDF, kfg_name, 'left_exact_subsidiary_8knot_cord_type') + 
                calc_num_sole_8knot_cords(aDF, kfg_name, 'right_exact_subsidiary_8knot_cord_type'))
        
        total_exact_trailing8knots = (
            calc_num_trailing_8knot_cords(aDF, kfg_name, 'left_exact_pendant_8knot_cord_type') + 
            calc_num_trailing_8knot_cords(aDF, kfg_name, 'right_exact_pendant_8knot_cord_type') + 
            calc_num_trailing_8knot_cords(aDF, kfg_name, 'left_exact_subsidiary_8knot_cord_type') + 
            calc_num_trailing_8knot_cords(aDF, kfg_name, 'right_exact_subsidiary_8knot_cord_type'))
        
        total_close_sole8knots = (
                calc_num_sole_8knot_cords(aDF, kfg_name, 'left_close_pendant_8knot_cord_type') + 
                calc_num_sole_8knot_cords(aDF, kfg_name, 'right_close_pendant_8knot_cord_type') + 
                calc_num_sole_8knot_cords(aDF, kfg_name, 'left_close_subsidiary_8knot_cord_type') + 
                calc_num_sole_8knot_cords(aDF, kfg_name, 'right_close_subsidiary_8knot_cord_type'))
        
        total_close_trailing8knots = (
            calc_num_trailing_8knot_cords(aDF, kfg_name, 'left_close_pendant_8knot_cord_type') + 
            calc_num_trailing_8knot_cords(aDF, kfg_name, 'right_close_pendant_8knot_cord_type') + 
            calc_num_trailing_8knot_cords(aDF, kfg_name, 'left_close_subsidiary_8knot_cord_type') + 
            calc_num_trailing_8knot_cords(aDF, kfg_name, 'right_close_subsidiary_8knot_cord_type'))
        
        the_row_dict = {'kfg_name': kfg_name}

        the_row_dict['num_ascher_sums'] = num_sums
        the_row_dict['num_pps_sums'] = num_pps_sums
        the_row_dict['num_cps_sums'] = num_cps_sums
        the_row_dict['num_ips_sums'] = num_ips_sums

        the_row_dict['total_8knot_cords'] = calc_total_8knot_cords(aDF, kfg_name)
        the_row_dict['total_sum8knots'] = total_exact_sole8knots + total_close_sole8knots + total_exact_trailing8knots + total_close_sole8knots
        the_row_dict['total_sole8knots'] = total_exact_sole8knots + total_close_sole8knots
        the_row_dict['total_trailing8knots'] = total_exact_trailing8knots + total_close_trailing8knots
        the_row_dict['total_exact_sole8knots'] = total_exact_sole8knots
        the_row_dict['total_exact_trailing8knots'] = total_exact_trailing8knots
        the_row_dict['total_close_sole8knots'] = total_close_sole8knots
        the_row_dict['total_close_trailing8knots'] = total_close_trailing8knots

        for aColumn in eight_knot_cord_type_columns:
            the_row_dict[aColumn+'_sole8knot'] = calc_num_sole_8knot_cords(aDF, kfg_name, aColumn)
            the_row_dict[aColumn+'_trailing8knot'] = calc_num_trailing_8knot_cords(aDF, kfg_name, aColumn)
        eight_knot_types.append(the_row_dict)

    return pd.DataFrame(eight_knot_types)

left_handed_df = pendant_ascher_sums_df[pendant_ascher_sums_df['handedness'] < 0]
right_handed_df = pendant_ascher_sums_df[pendant_ascher_sums_df['handedness'] >= 0]
left_handed_eight_knot_types_df = build_augmented_eight_knot_df(left_handed_df)
right_handed_eight_knot_types_df = build_augmented_eight_knot_df(right_handed_df)
all_eight_knot_types_df = build_augmented_eight_knot_df(pendant_ascher_sums_df)   
left_handed_eight_knot_types_df.to_csv(f"{uloc.fieldmarks_data_dir()}/left_handed_sums_8knot_types.csv", index=False)
right_handed_eight_knot_types_df.to_csv(f"{uloc.fieldmarks_data_dir()}/right_handed_sums_8knot_types.csv", index=False)
all_eight_knot_types_df.to_csv(f"{uloc.fieldmarks_data_dir()}/all_sums_8knot_types.csv", index=False)   
```
Code
```{python}
class EightKnotSummaryViewer(Pandas2HTML):
    def default_type_is_number(self, column_name):
        start_adjectives = ["index", "total", "right", "left"]
        end_adjectives = ["index"]
        return (any([column_name.lower().startswith(x) for x in start_adjectives]) or 
                any([column_name.lower().endswith(x) for x in end_adjectives]))

    def default_type_is_bar(self, column_name):
        return False

    def default_label(self, column_name):
        the_label = super().default_label(column_name)
        is_frequency_count = (column_name.startswith("left") or column_name.startswith("right"))
        if is_frequency_count: the_label = "# " + the_label.strip() + "s"
        return the_label

column_specs = {}
file_specs = {
        "output_dir":f"{uloc.fieldmark_staging_dir()}/analyses/database",
        "basename":"left_handed_eightknotsumsummary"
        }   
page_specs = {
        "title":"Left-Handed Figure-8 Knot Sum Summary",
        "description":"Left-Handed Summary Frequency Counts for Khipus whose Ascher Summand Ranges are Marked by Cords W/Figure-8 Knot",
        }
left_handed_eight_knot_types_df.fillna('', inplace=True)
EightKnotSummaryViewer(file_specs, page_specs, column_specs, from_df=left_handed_eight_knot_types_df).make_pandas_html(show_html=False);

file_specs = {
        "output_dir":f"{uloc.fieldmark_staging_dir()}/analyses/database",
        "basename":"right_handed_eightknotsumsummary"
        }   
page_specs = {
        "title":"Right-Handed Figure-8 Knot Sum Summary",
        "description":"Right-Handed Summary Frequency Counts for Khipus whose Ascher Summand Ranges are Marked by Cords W/Figure-8 Knot",
        }
right_handed_eight_knot_types_df.fillna('', inplace=True)
EightKnotSummaryViewer(file_specs, page_specs, column_specs, from_df=right_handed_eight_knot_types_df).make_pandas_html(show_html=False);

file_specs = {
        "output_dir":f"{uloc.fieldmark_staging_dir()}/analyses/database",
        "basename":"eightknotsumsummary"
        }   
page_specs = {
        "title":"Figure-8 Knot Sum Summary",
        "description":"Ascher Sum Summary Frequency Counts for Khipus whose Ascher Summand Ranges are Marked by Cords W/Figure-8 Knot",
        }
all_eight_knot_types_df.fillna('', inplace=True)
EightKnotSummaryViewer(file_specs, page_specs, column_specs, from_df=all_eight_knot_types_df).make_pandas_html(show_html=False);
```
Generated Pandas2HTML HTML table: file:///Users/ashokkhosla/Desktop/LOOM/khipufieldguide/notebook/fieldmarks/analyses/database/left_handed_eightknotsumsummary.html
Generated Pandas2HTML HTML table: file:///Users/ashokkhosla/Desktop/LOOM/khipufieldguide/notebook/fieldmarks/analyses/database/right_handed_eightknotsumsummary.html
Generated Pandas2HTML HTML table: file:///Users/ashokkhosla/Desktop/LOOM/khipufieldguide/notebook/fieldmarks/analyses/database/eightknotsumsummary.html
Code
```{python}
all_eight_knot_types_df.columns
```
Index(['kfg_name', 'num_ascher_sums', 'num_pps_sums', 'num_cps_sums',
       'num_ips_sums', 'total_8knot_cords', 'total_sum8knots',
       'total_sole8knots', 'total_trailing8knots', 'total_exact_sole8knots',
       'total_exact_trailing8knots', 'total_close_sole8knots',
       'total_close_trailing8knots',
       'left_exact_pendant_8knot_cord_type_sole8knot',
       'left_exact_pendant_8knot_cord_type_trailing8knot',
       'right_exact_pendant_8knot_cord_type_sole8knot',
       'right_exact_pendant_8knot_cord_type_trailing8knot',
       'left_exact_subsidiary_8knot_cord_type_sole8knot',
       'left_exact_subsidiary_8knot_cord_type_trailing8knot',
       'right_exact_subsidiary_8knot_cord_type_sole8knot',
       'right_exact_subsidiary_8knot_cord_type_trailing8knot',
       'left_close_pendant_8knot_cord_type_sole8knot',
       'left_close_pendant_8knot_cord_type_trailing8knot',
       'right_close_pendant_8knot_cord_type_sole8knot',
       'right_close_pendant_8knot_cord_type_trailing8knot',
       'left_close_subsidiary_8knot_cord_type_sole8knot',
       'left_close_subsidiary_8knot_cord_type_trailing8knot',
       'right_close_subsidiary_8knot_cord_type_sole8knot',
       'right_close_subsidiary_8knot_cord_type_trailing8knot'],
      dtype='object')
Code
```{python}
from utils_khipu import pct_kfg_khipus


do_make_khipus_by_8knot_type_sankey_diagram = True
# Provide data for Khipu Sums by % of Figure-8-Knot Marker Type 

#   source=c("All Khipus", "All Khipus", 
#            "(78%) W/Pendant Sums", "(78%) W/Pendant Sums", 
#            "(77%) With 8Knot Pendant Sum Markers", "(77%) With 8Knot Pendant Sum Markers", "(77%) With 8Knot Pendant Sum Markers"),
#   target=c("(78%) With Pendant Sums", "(22%) With Out Pendant Sums", 
#            "(77%) With 8Knot Pendant Sum Markers", "(23%) With Out 8Knot Pendant Sums Markers",
#            "(16%) Only Left-Handed 8Knot Pendant Sums","(64%) Left AND Right-Handed 8Knot Pendant Sums",  "(20%) Only Right-Handed 8Knot Pendant Sums"),
#   value=c(416, 664-416, 320, 416-320, 52, 204, 64)

if do_make_khipus_by_8knot_type_sankey_diagram:
    def has_left_handed_8knot_pendant_sums(aKFGName):
        aDF = all_eight_knot_types_df[all_eight_knot_types_df['kfg_name'] == aKFGName]
        has_left_handed_8knot_pendant_sum = (
            (len(aDF[aDF['left_exact_pendant_8knot_cord_type_sole8knot'] > 0]) > 0) or
            (len(aDF[aDF['left_exact_pendant_8knot_cord_type_trailing8knot'] > 0]) > 0) or
            (len(aDF[aDF['left_exact_subsidiary_8knot_cord_type_sole8knot'] > 0]) > 0) or
            (len(aDF[aDF['left_exact_subsidiary_8knot_cord_type_trailing8knot'] > 0]) > 0) or
            (len(aDF[aDF['left_close_pendant_8knot_cord_type_sole8knot'] > 0]) > 0) or
            (len(aDF[aDF['left_close_pendant_8knot_cord_type_trailing8knot'] > 0]) > 0) or
            (len(aDF[aDF['left_close_subsidiary_8knot_cord_type_sole8knot'] > 0]) > 0) or
            (len(aDF[aDF['left_close_subsidiary_8knot_cord_type_trailing8knot'] > 0]) > 0)
            )

        return 1 if  has_left_handed_8knot_pendant_sum else 0   

    def has_right_handed_8knot_pendant_sums(aKFGName):
        aDF = all_eight_knot_types_df[all_eight_knot_types_df['kfg_name'] == aKFGName]
        has_right_handed_8knot_pendant_sum = (
            (len(aDF[aDF['right_exact_pendant_8knot_cord_type_sole8knot'] > 0]) > 0) or
            (len(aDF[aDF['right_exact_pendant_8knot_cord_type_trailing8knot'] > 0]) > 0) or
            (len(aDF[aDF['right_exact_subsidiary_8knot_cord_type_sole8knot'] > 0]) > 0) or
            (len(aDF[aDF['right_exact_subsidiary_8knot_cord_type_trailing8knot'] > 0]) > 0) or
            (len(aDF[aDF['right_close_pendant_8knot_cord_type_sole8knot'] > 0]) > 0) or
            (len(aDF[aDF['right_close_pendant_8knot_cord_type_trailing8knot'] > 0]) > 0) or
            (len(aDF[aDF['right_close_subsidiary_8knot_cord_type_sole8knot'] > 0]) > 0) or
            (len(aDF[aDF['right_close_subsidiary_8knot_cord_type_trailing8knot'] > 0]) > 0)
            )
        
        return 1 if has_right_handed_8knot_pendant_sum  else 0

    def has_both_handed_8knot_trailing_sums(aKFGName):
        has_both_handed_8knot_trailing_sum = has_left_handed_8knot_pendant_sums(aKFGName) and has_right_handed_8knot_pendant_sums(aKFGName)
        return 1 if has_both_handed_8knot_trailing_sum else 0



    num_all_khipus = len(all_khipus)
    # Determine the number of khipus with 8-knots
    num_khipus_with_all_pendant_sums = len(all_eight_knot_types_df[all_eight_knot_types_df['num_ascher_sums'] > 0])
    num_khipus_without_pendant_sums  = num_all_khipus - num_khipus_with_all_pendant_sums
    
    num_all_pendant_sums_with_8knot_markers = len(all_eight_knot_types_df[all_eight_knot_types_df['total_sum8knots'] > 0])
    num_all_pendant_sums_without_8knot_markers = num_khipus_with_all_pendant_sums - num_all_pendant_sums_with_8knot_markers
    
    num_left_handed_8knot_pendant_sums = sum(has_left_handed_8knot_pendant_sums(kfg_name) and (not has_right_handed_8knot_pendant_sums(kfg_name)) for kfg_name in all_eight_knot_types_df['kfg_name'])
    num_right_handed_8knot_pendant_sums = sum(has_right_handed_8knot_pendant_sums(kfg_name) and (not has_left_handed_8knot_pendant_sums(kfg_name)) for kfg_name in all_eight_knot_types_df['kfg_name'])
    num_both_handed_8knot_pendant_sums = sum(has_both_handed_8knot_trailing_sums(kfg_name) for kfg_name in all_eight_knot_types_df['kfg_name'])

    sums_pct = round(100.0* num_khipus_with_all_pendant_sums / float(num_all_khipus))
    sum_markers_pct = round(100.0* num_all_pendant_sums_with_8knot_markers / float(num_khipus_with_all_pendant_sums))
    left_handed_sums_pct = round(100.0* num_left_handed_8knot_pendant_sums / float(num_all_pendant_sums_with_8knot_markers))
    right_handed_sums_pct = round(100.0* num_right_handed_8knot_pendant_sums / float(num_all_pendant_sums_with_8knot_markers))
    both_handed_sums_pct = round(100.0* num_both_handed_8knot_pendant_sums / float(num_all_pendant_sums_with_8knot_markers))

    print(f"\tsource=c(\"All Khipus\", \"All Khipus\",")
    print(f"\t\t\"( {sums_pct}% ) W/Pendant Sum\", \"( {sums_pct}% ) W/Pendant Sum\", ")
    print(f"\t\t\"( {sum_markers_pct}% ) W/8Knot Pendant Sum Marker\", \"( {sum_markers_pct}% ) W/8Knot Pendant Sum Marker\", \"( {sum_markers_pct}% ) W/8Knot Pendant Sum Marker\"),")
   
    print(f"\ttarget=c(\"( {sums_pct}% ) W/Pendant Sums\", \"( {100-sums_pct}% ) W-Out/Pendant Sums\", ")
    print(f"\t\t\"( {sum_markers_pct}% )  W/8Knot Pendant Sum Markers\", \"( {100-sum_markers_pct}% )   W-Out/8Knot Pendant Sums Markers\",")
    print(f"\t\t\"( {left_handed_sums_pct}% ) Only Left-Handed 8Knot Pendant Sums\", \"( {both_handed_sums_pct}% )  Left AND Right-Handed 8Knot Pendant Sums\",  \"( {right_handed_sums_pct}% ) Only Right-Handed 8Knot Pendant Sums\"),")

    print(f"\tvalue=c({num_khipus_with_all_pendant_sums}, {num_all_khipus-num_khipus_with_all_pendant_sums}, {num_all_pendant_sums_with_8knot_markers}, {num_khipus_with_all_pendant_sums-num_all_pendant_sums_with_8knot_markers}, {num_left_handed_8knot_pendant_sums}, {num_both_handed_8knot_pendant_sums}, {num_right_handed_8knot_pendant_sums})")

```
    source=c("All Khipus", "All Khipus",
        "( 61% ) W/Pendant Sum", "( 61% ) W/Pendant Sum", 
        "( 72% ) W/8Knot Pendant Sum Marker", "( 72% ) W/8Knot Pendant Sum Marker", "( 72% ) W/8Knot Pendant Sum Marker"),
    target=c("( 61% ) W/Pendant Sums", "( 39% ) W-Out/Pendant Sums", 
        "( 72% )  W/8Knot Pendant Sum Markers", "( 28% )   W-Out/8Knot Pendant Sums Markers",
        "( 12% ) Only Left-Handed 8Knot Pendant Sums", "( 82% )  Left AND Right-Handed 8Knot Pendant Sums",  "( 13% ) Only Right-Handed 8Knot Pendant Sums"),
    value=c(434, 277, 313, 121, 39, 256, 40)

3.2 Viewers for Left-Handed and Right-Handed Eight Knot Ascher Sum Summaries

Viewers are provided for the Left-Handed Eight-Knot Sum Summary “contingency table” of sums marked by figure-8-knot cords and the corresponding Right Handed Eight-Knot Sum Summary.

Code
```{python}
import tabulate

def eight_knot_type_cord_totals(anEightKnotTypesDF, aTitle):
    total_edge_pendant_sole_8knot_cords =  anEightKnotTypesDF['left_exact_pendant_8knot_cord_type_sole8knot'].sum() + anEightKnotTypesDF['right_exact_pendant_8knot_cord_type_sole8knot'].sum()
    total_edge_subsidiary_sole_8knot_cords = anEightKnotTypesDF['left_exact_subsidiary_8knot_cord_type_sole8knot'].sum() + anEightKnotTypesDF['right_exact_subsidiary_8knot_cord_type_sole8knot'].sum()
    total_edge_sole_8knot__cords =  total_edge_pendant_sole_8knot_cords + total_edge_subsidiary_sole_8knot_cords
    total_close_pendant_sole_8knot_cords =  anEightKnotTypesDF['left_close_pendant_8knot_cord_type_sole8knot'].sum() + anEightKnotTypesDF['right_close_pendant_8knot_cord_type_sole8knot'].sum()
    total_close_subsidiary_sole_8knot_cords = anEightKnotTypesDF['left_close_subsidiary_8knot_cord_type_sole8knot'].sum() + anEightKnotTypesDF['right_close_subsidiary_8knot_cord_type_sole8knot'].sum()
    total_close_sole_8_knot_cords = total_close_pendant_sole_8knot_cords + total_close_subsidiary_sole_8knot_cords
    total_sole_8knot_cords =  anEightKnotTypesDF['total_sole8knots'].sum()

    total_edge_pendant_trailing_8knot_cords = anEightKnotTypesDF['left_exact_pendant_8knot_cord_type_trailing8knot'].sum() + anEightKnotTypesDF['right_exact_pendant_8knot_cord_type_trailing8knot'].sum()
    total_edge_subsidiary_trailing_8knot_cords = anEightKnotTypesDF['left_exact_subsidiary_8knot_cord_type_trailing8knot'].sum() + anEightKnotTypesDF['right_exact_subsidiary_8knot_cord_type_trailing8knot'].sum()
    total_edge_trailing_8knot_cords = total_edge_pendant_trailing_8knot_cords + total_edge_subsidiary_trailing_8knot_cords
    total_close_pendant_trailing_8knot_cords = anEightKnotTypesDF['left_close_pendant_8knot_cord_type_trailing8knot'].sum() + anEightKnotTypesDF['right_close_pendant_8knot_cord_type_trailing8knot'].sum()
    total_close_subsidiary_trailing_8knot_cords = anEightKnotTypesDF['left_close_subsidiary_8knot_cord_type_trailing8knot'].sum() + anEightKnotTypesDF['right_close_subsidiary_8knot_cord_type_trailing8knot'].sum()
    total_close_trailing_8knot_cords = total_close_pendant_trailing_8knot_cords + total_close_subsidiary_trailing_8knot_cords
    total_trailing_8knot_cords = total_edge_trailing_8knot_cords + total_close_trailing_8knot_cords

    total_num_8knot_cords = total_sole_8knot_cords + total_trailing_8knot_cords

    print(f"\n{aTitle}\n{'='*len(aTitle)}")

    knot_dict_columns = ["8-KNOT CORD TYPE", "# 8Knot Cord Sum Markers", "# Edge Matches",  "# Pendant Edge Matches", "# Subsidiary Edge Matches", "# Neighbor Matches", "# Neighbor Pendant Matches", "# Neighbor Subsidiary Matches"]
    sole_8_knot_dict = {"8-KNOT CORD TYPE": "SOLE 8KNOT CORDS",
                        "# 8Knot Cord Sum Markers": uloom.percent_info(total_sole_8knot_cords, total_num_8knot_cords),
                        "# Edge Matches": uloom.percent_info(total_edge_sole_8knot__cords, total_sole_8knot_cords),
                        "# Pendant Edge Matches": uloom.percent_info(total_edge_pendant_sole_8knot_cords, total_edge_sole_8knot__cords),
                        "# Subsidiary Edge Matches": uloom.percent_info(total_edge_subsidiary_sole_8knot_cords, total_edge_sole_8knot__cords),
                        "# Neighbor Matches": uloom.percent_info(total_close_sole_8_knot_cords, total_sole_8knot_cords),
                        "# Neighbor Pendant Matches": uloom.percent_info(total_close_pendant_sole_8knot_cords, total_close_sole_8_knot_cords),
                        "# Neighbor Subsidiary Matches": uloom.percent_info(total_close_subsidiary_sole_8knot_cords, total_close_sole_8_knot_cords)}
    trailing_8_knot_dict = {"8-KNOT CORD TYPE": "TRAILING 8KNOT CORDS",
                            "# 8Knot Cord Sum Markers": uloom.percent_info(total_trailing_8knot_cords, total_num_8knot_cords),
                            "# Edge Matches": uloom.percent_info(total_edge_trailing_8knot_cords, total_trailing_8knot_cords),
                            "# Pendant Edge Matches": uloom.percent_info(total_edge_pendant_trailing_8knot_cords, total_edge_trailing_8knot_cords),
                            "# Subsidiary Edge Matches": uloom.percent_info(total_edge_subsidiary_trailing_8knot_cords, total_edge_trailing_8knot_cords),
                            "# Neighbor Matches": uloom.percent_info(total_close_trailing_8knot_cords, total_trailing_8knot_cords),
                            "# Neighbor Pendant Matches": uloom.percent_info(total_close_pendant_trailing_8knot_cords, total_close_trailing_8knot_cords),
                            "# Neighbor Subsidiary Matches": uloom.percent_info(total_close_subsidiary_trailing_8knot_cords, total_close_trailing_8knot_cords)
                    }
    the_df = pd.DataFrame([sole_8_knot_dict, trailing_8_knot_dict], columns=knot_dict_columns)
    return the_df

def eight_knot_type_khipu_totals(anEightKnotTypesDF, aTitle):
    total_edge_pendant_sole_8knot_cords =  anEightKnotTypesDF['left_exact_pendant_8knot_cord_type_sole8knot'].sum() + anEightKnotTypesDF['right_exact_pendant_8knot_cord_type_sole8knot'].sum()
    total_edge_subsidiary_sole_8knot_cords = anEightKnotTypesDF['left_exact_subsidiary_8knot_cord_type_sole8knot'].sum() + anEightKnotTypesDF['right_exact_subsidiary_8knot_cord_type_sole8knot'].sum()
    total_edge_sole_8knot__cords =  total_edge_pendant_sole_8knot_cords + total_edge_subsidiary_sole_8knot_cords
    total_close_pendant_sole_8knot_cords =  anEightKnotTypesDF['left_close_pendant_8knot_cord_type_sole8knot'].sum() + anEightKnotTypesDF['right_close_pendant_8knot_cord_type_sole8knot'].sum()
    total_close_subsidiary_sole_8knot_cords = anEightKnotTypesDF['left_close_subsidiary_8knot_cord_type_sole8knot'].sum() + anEightKnotTypesDF['right_close_subsidiary_8knot_cord_type_sole8knot'].sum()
    total_close_sole_8_knot_cords = total_close_pendant_sole_8knot_cords + total_close_subsidiary_sole_8knot_cords
    total_sole_8knot_cords =  anEightKnotTypesDF['total_sole8knots'].sum()

    total_edge_pendant_trailing_8knot_cords = anEightKnotTypesDF['left_exact_pendant_8knot_cord_type_trailing8knot'].sum() + anEightKnotTypesDF['right_exact_pendant_8knot_cord_type_trailing8knot'].sum()
    total_edge_subsidiary_trailing_8knot_cords = anEightKnotTypesDF['left_exact_subsidiary_8knot_cord_type_trailing8knot'].sum() + anEightKnotTypesDF['right_exact_subsidiary_8knot_cord_type_trailing8knot'].sum()
    total_edge_trailing_8knot_cords = total_edge_pendant_trailing_8knot_cords + total_edge_subsidiary_trailing_8knot_cords
    total_close_pendant_trailing_8knot_cords = anEightKnotTypesDF['left_close_pendant_8knot_cord_type_trailing8knot'].sum() + anEightKnotTypesDF['right_close_pendant_8knot_cord_type_trailing8knot'].sum()
    total_close_subsidiary_trailing_8knot_cords = anEightKnotTypesDF['left_close_subsidiary_8knot_cord_type_trailing8knot'].sum() + anEightKnotTypesDF['right_close_subsidiary_8knot_cord_type_trailing8knot'].sum()
    total_close_trailing_8knot_cords = total_close_pendant_trailing_8knot_cords + total_close_subsidiary_trailing_8knot_cords
    total_trailing_8knot_cords = total_edge_trailing_8knot_cords + total_close_trailing_8knot_cords

    total_num_8knot_cords = total_sole_8knot_cords + total_trailing_8knot_cords

    print(f"\n{aTitle}\n{'='*len(aTitle)}")

    knot_dict_columns = ["8-KNOT CORD TYPE", "# 8Knot Cord Sum Markers", "# Edge Matches", "# Neighbor Matches", "# Pendant Edge Matches", "# Subsidiary Edge Matches", "# Neighbor Pendant Matches", "# Neighbor Subsidiary Matches"]
    sole_8_knot_dict = {"8-KNOT CORD TYPE": "SOLE 8KNOT CORDS",
                        "# 8Knot Cord Sum Markers": uloom.percent_info(total_sole_8knot_cords, total_num_8knot_cords),
                        "# Edge Matches": uloom.percent_info(total_edge_sole_8knot__cords, total_sole_8knot_cords),
                        "# Pendant Edge Matches": uloom.percent_info(total_edge_pendant_sole_8knot_cords, total_edge_sole_8knot__cords),
                        "# Subsidiary Edge Matches": uloom.percent_info(total_edge_subsidiary_sole_8knot_cords, total_edge_sole_8knot__cords),
                        "# Neighbor Matches": uloom.percent_info(total_close_sole_8_knot_cords, total_sole_8knot_cords),
                        "# Neighbor Pendant Matches": uloom.percent_info(total_close_pendant_sole_8knot_cords, total_close_sole_8_knot_cords),
                        "# Neighbor Subsidiary Matches": uloom.percent_info(total_close_subsidiary_sole_8knot_cords, total_close_sole_8_knot_cords)}
    trailing_8_knot_dict = {"8-KNOT CORD TYPE": "TRAILING 8KNOT CORDS",
                            "# 8Knot Cord Sum Markers": uloom.percent_info(total_trailing_8knot_cords, total_num_8knot_cords),
                            "# Edge Matches": uloom.percent_info(total_edge_trailing_8knot_cords, total_trailing_8knot_cords),
                            "# Pendant Edge Matches": uloom.percent_info(total_edge_pendant_trailing_8knot_cords, total_edge_trailing_8knot_cords),
                            "# Subsidiary Edge Matches": uloom.percent_info(total_edge_subsidiary_trailing_8knot_cords, total_edge_trailing_8knot_cords),
                            "# Neighbor Matches": uloom.percent_info(total_close_trailing_8knot_cords, total_trailing_8knot_cords),
                            "# Neighbor Pendant Matches": uloom.percent_info(total_close_pendant_trailing_8knot_cords, total_close_trailing_8knot_cords),
                            "# Neighbor Subsidiary Matches": uloom.percent_info(total_close_subsidiary_trailing_8knot_cords, total_close_trailing_8knot_cords)
                    }
    the_df = pd.DataFrame([sole_8_knot_dict, trailing_8_knot_dict], columns=knot_dict_columns)
    return the_df

def print_eight_knot_type_totals(aDF, table_format='html'):
    if table_format == 'html':
        the_html_rep = tabulate.tabulate(aDF, headers='keys', tablefmt='html')   
        the_html_rep = the_html_rep.replace("<table>", "<table class=\"sortable\" style=\"border: 1px solid #ddd;font-size:.8rem;\">")  
        the_html_rep = the_html_rep.replace("<tr>", "<tr style=\"border: 1px solid #ddd;font-size:.8rem;background-color: #444444;color: white;\">", count=1)  
        print(the_html_rep)
    elif table_format == 'markdown':
        print(aDF.to_markdown(index=False)) 
    else:
        the_html_rep = tabulate.tabulate(aDF, headers='keys', tablefmt=table_format)   
        print(the_html_rep)

do_print = False
if do_print:
    lh_8knotsums_cords_total_df = eight_knot_type_cord_totals(left_handed_eight_knot_types_df, "Summary of Left-Handed Eight Knot Ascher Sum Types")
    print_eight_knot_type_totals(lh_8knotsums_cords_total_df, table_format='html')
    rh_8knotsums_cords_total_df = eight_knot_type_cord_totals(right_handed_eight_knot_types_df, "Summary of Right-Handed Eight Knot Ascher Sum Types") 
    print_eight_knot_type_totals(rh_8knotsums_cords_total_df, table_format='html')

```

3.3 Summary of Left-Handed and Right Handed Eight Knot Ascher Sum Types

3.3.1 Left-Handed Eight Knot Ascher Sums Summary

Summary of Left-Handed Eight Knot Ascher Sum Types

8-KNOT CORD TYPE # 8Knot Cord Sum Markers # Edge Matches # Pendant Edge Matches # Subsidiary Edge Matches # Neighbor Matches # Neighbor Pendant Matches # Neighbor Subsidiary Matches
0 SOLE 8KNOT CORDS 59% (2346 of 3969) 56% (1312 of 2346) 66% (867 of 1312) 34% (445 of 1312) 44% (1034 of 2346) 59% (611 of 1034) 41% (423 of 1034)
1 TRAILING 8KNOT CORDS 41% (1623 of 3969) 56% (909 of 1623) 88% (796 of 909) 12% (113 of 909) 44% (714 of 1623) 84% (599 of 714) 16% (115 of 714)

3.3.2 Right-Handed Eight Knot Ascher Sums Summary

8-KNOT CORD TYPE # 8Knot Cord Sum Markers # Edge Matches # Pendant Edge Matches # Subsidiary Edge Matches # Neighbor Matches # Neighbor Pendant Matches # Neighbor Subsidiary Matches
0 SOLE 8KNOT CORDS 58% (2997 of 5156) 54% (1613 of 2997) 58% (928 of 1613) 42% (685 of 1613) 46% (1384 of 2997) 51% (709 of 1384) 49% (675 of 1384)
1 TRAILING 8KNOT CORDS 42% (2159 of 5156) 51% (1107 of 2159) 86% (951 of 1107) 14% (156 of 1107) 49% (1052 of 2159) 83% (873 of 1052) 17% (179 of 1052)

4.0 Sankey Diagram of Figure-8-Knot Sum Cords by Type

Code
```{python}
num_all_khipus = len(all_khipus)
num_khipus_with_pendant_ascher_sums = pendant_ascher_sums_df['kfg_name'].nunique()

only_eight_knot_ascher_sums_df = pendant_ascher_sums_df[pendant_ascher_sums_df['has_figure8knot_indicator']]    
num_khipus_with_figure8knot_pendant_ascher_sums = only_eight_knot_ascher_sums_df['kfg_name'].nunique()

left_handed_8knotsum_khipus_df = only_eight_knot_ascher_sums_df[only_eight_knot_ascher_sums_df['handedness'] < 0]
right_handed_8knotsum_khipus_df = only_eight_knot_ascher_sums_df[only_eight_knot_ascher_sums_df['handedness'] >= 0]

num_left_handed_8knotsum_khipus = left_handed_8knotsum_khipus_df['kfg_name'].nunique()
num_right_handed_8knotsum_khipus = right_handed_8knotsum_khipus_df['kfg_name'].nunique()
left_handed_khipus = set(left_handed_8knotsum_khipus_df['kfg_name'])
right_handed_khipus = set(right_handed_8knotsum_khipus_df['kfg_name'])

num_left_only_8knotsum_khipus = len(left_handed_khipus - right_handed_khipus)
num_left_and_right_8knotsum_khipus = len(left_handed_khipus.intersection(right_handed_khipus))
num_right_only_8knotsum_khipus = len(right_handed_khipus - left_handed_khipus)

do_print = False
if do_print:
    print(f"# of Khipus: {num_all_khipus}")
    print(f"# of Khipus with All Pendant Ascher Sums: {num_khipus_with_pendant_ascher_sums}")
    print(f"# of Khipus with All Pendant Ascher Sums marked by Figure-8 Knots: {uloom.percent_info(num_khipus_with_figure8knot_pendant_ascher_sums, num_khipus_with_pendant_ascher_sums)}")
    print(f"# of Khipus with All Left-Handed Pendant Ascher Sums marked by Figure-8 Knots: {uloom.percent_info(num_left_handed_8knotsum_khipus, num_khipus_with_figure8knot_pendant_ascher_sums)}")
    print(f"# of Khipus with All Right-Handed Pendant Ascher Sums marked by Figure-8 Knots: {uloom.percent_info(num_right_handed_8knotsum_khipus, num_khipus_with_figure8knot_pendant_ascher_sums)}")
    print(f"# of Khipus with All Left-Only Pendant Ascher Sums marked by Figure-8 Knots: {uloom.percent_info(num_left_only_8knotsum_khipus, num_khipus_with_figure8knot_pendant_ascher_sums)}")
    print(f"# of Khipus with All Left & Right Pendant Ascher Sums marked by Figure-8 Knots: {uloom.percent_info(num_left_and_right_8knotsum_khipus, num_khipus_with_figure8knot_pendant_ascher_sums)}")
    print(f"# of Khipus with All Right-Only Pendant Ascher Sums marked by Figure-8 Knots: {uloom.percent_info(num_right_only_8knotsum_khipus, num_khipus_with_figure8knot_pendant_ascher_sums)}")

```
Code
```{python}
def has_sole_knot(aRow):
    return ((aRow['left_exact_pendant_8knot_cord_type'] == 'sole_8knot') or
            (aRow['right_exact_pendant_8knot_cord_type'] == 'sole_8knot') or
            (aRow['left_exact_subsidiary_8knot_cord_type'] == 'sole_8knot') or
            (aRow['right_exact_subsidiary_8knot_cord_type'] == 'sole_8knot') or
            (aRow['left_close_pendant_8knot_cord_type'] == 'sole_8knot') or
            (aRow['right_close_pendant_8knot_cord_type'] == 'sole_8knot') or
            (aRow['left_close_subsidiary_8knot_cord_type'] == 'sole_8knot') or
            (aRow['right_close_subsidiary_8knot_cord_type'] == 'sole_8knot'))

def has_trailing_knot(aRow):
    return ((aRow['left_exact_pendant_8knot_cord_type'] == 'trailing_8knot') or
            (aRow['right_exact_pendant_8knot_cord_type'] == 'trailing_8knot') or
            (aRow['left_exact_subsidiary_8knot_cord_type'] == 'trailing_8knot') or
            (aRow['right_exact_subsidiary_8knot_cord_type'] == 'trailing_8knot') or
            (aRow['left_close_pendant_8knot_cord_type'] == 'trailing_8knot') or
            (aRow['right_close_pendant_8knot_cord_type'] == 'trailing_8knot') or
            (aRow['left_close_subsidiary_8knot_cord_type'] == 'trailing_8knot') or
            (aRow['right_close_subsidiary_8knot_cord_type'] == 'trailing_8knot'))

def has_edge_match(aRow):
    return (aRow['has_left_exact_8knot_cord'] or aRow['has_right_exact_8knot_cord'])

def has_close_match(aRow):
    return (aRow['has_left_close_8knot_cord'] or aRow['has_right_close_8knot_cord'])

def has_pendant_edge_match(aRow):
    return has_edge_match(aRow) and ((len(aRow['left_exact_pendant_8knot_cord_name'])>0) or (len(aRow['right_exact_pendant_8knot_cord_name'])>0))

def has_subsidiary_edge_match(aRow):
    return (has_edge_match(aRow) and 
            not(has_pendant_edge_match(aRow)) and
            ((len(aRow['left_exact_subsidiary_8knot_cord_names'])>0) or (len(aRow['right_exact_subsidiary_8knot_cord_names'])>0)))

def has_pendant_close_match(aRow):
    return (not (has_edge_match(aRow)) and 
            has_close_match(aRow) and 
            ((len(aRow['left_close_pendant_cord_names'])>0) or (len(aRow['right_close_pendant_cord_names'])>0)))

def has_subsidiary_close_match(aRow):
    return (not (has_edge_match(aRow)) and 
            has_close_match(aRow) and 
            (not has_pendant_close_match(aRow)) and 
            ((len(aRow['left_close_subsidiary_8knot_cord_names'])>0) or (len(aRow['right_close_subsidiary_8knot_cord_names'])>0)))

# LEVEL 0
num_fig8knot_ascher_sums = only_eight_knot_ascher_sums_df.shape[0]

# LEVEL 1 - SOLE-8-KNOT, BOTH-8-KNOT, TRAILING-8-KNOT
sole8knot_sum__mask = [has_sole_knot(row) for _, row in pendant_ascher_sums_df.iterrows()]
trailing8knot_sum_mask = [has_trailing_knot(row) for _, row in pendant_ascher_sums_df.iterrows()]

only_sole8knot_sum__mask =  [(has_sole_knot(row) and (not has_trailing_knot(row)))  for _, row in pendant_ascher_sums_df.iterrows()]
both8knot_sum__mask =  [(has_sole_knot(row) and has_trailing_knot(row))  for _, row in pendant_ascher_sums_df.iterrows()]
only_trailing8knot_sum__mask =  [((not has_sole_knot(row)) and has_trailing_knot(row))  for _, row in pendant_ascher_sums_df.iterrows()]

only_sole8knot_sum_df = pendant_ascher_sums_df[only_sole8knot_sum__mask]
both8knot_sum_df = pendant_ascher_sums_df[both8knot_sum__mask]
only_trailing8knot_sum_df = pendant_ascher_sums_df[only_trailing8knot_sum__mask]

num_only_sole8knot_ascher_sums = sum(only_sole8knot_sum__mask)
num_both8knot_ascher_sums = sum(both8knot_sum__mask)
num_only_trailing8knot_ascher_sums =  sum(only_trailing8knot_sum__mask)

# LEVEL 2 - EDGE vs CLOSE

# 2.1
edges_only_sole8knot_sum_mask = [has_edge_match(row) for _, row in only_sole8knot_sum_df.iterrows()] 
edges_only_sole8knot_sum_df = only_sole8knot_sum_df[edges_only_sole8knot_sum_mask]
num_edges_only_sole8knot_sums = sum(edges_only_sole8knot_sum_mask)
close_only_sole8knot_sum_mask = [((not has_edge_match(row)) and has_close_match(row)) for _, row in only_sole8knot_sum_df.iterrows()]
close_only_sole8knot_sum_df = only_sole8knot_sum_df[close_only_sole8knot_sum_mask]
num_close_only_sole8knot_sums = sum(close_only_sole8knot_sum_mask)

# 2.2
edges_both8knot_sum_mask = [has_edge_match(row) for _, row in both8knot_sum_df.iterrows()] 
edges_both8knot_sum_df = both8knot_sum_df[edges_both8knot_sum_mask]
close_both8knot_sum_mask = [((not has_edge_match(row)) and has_close_match(row)) for _, row in both8knot_sum_df.iterrows()]
close_both8knot_sum_df = both8knot_sum_df[close_both8knot_sum_mask]
num_edges_both8knot_sums = sum(edges_both8knot_sum_mask)
num_close_both8knot_sums = sum(close_both8knot_sum_mask)

# 2.3
edges_only_trailing8knot_sum_mask = [has_edge_match(row) for _, row in only_trailing8knot_sum_df.iterrows()] 
edges_only_trailing8knot_sum_df = only_trailing8knot_sum_df[edges_only_trailing8knot_sum_mask]
close_only_trailing8knot_sum_mask = [((not has_edge_match(row)) and has_close_match(row)) for _, row in only_trailing8knot_sum_df.iterrows()]
close_only_trailing8knot_sum_df = only_trailing8knot_sum_df[close_only_trailing8knot_sum_mask]
num_edges_only_trailing8knot_edge_match_sums = sum(edges_only_trailing8knot_sum_mask)
num_close_only_trailing8knot_edge_match_sums = sum(close_only_trailing8knot_sum_mask)

# LEVEL 3
# 3.1
pendant_edges_only_sole8knot_sum_mask = [has_pendant_edge_match(row) for _, row in edges_only_sole8knot_sum_df.iterrows()] 
subsidiary_edges_only_sole8knot_sum_mask = [has_subsidiary_edge_match(row) for _, row in edges_only_sole8knot_sum_df.iterrows()] 
num_pendant_edges_only_sole8knot_sums = sum(pendant_edges_only_sole8knot_sum_mask)
num_subsidiary_edges_only_sole8knot_sums = sum(subsidiary_edges_only_sole8knot_sum_mask)

pendant_close_only_sole8knot_sum_mask = [has_pendant_close_match(row) for _, row in close_only_sole8knot_sum_df.iterrows()] 
num_pendant_close_only_sole8knot_sums = sum(pendant_close_only_sole8knot_sum_mask)
subsidiary_close_only_sole8knot_sum_mask = [has_subsidiary_close_match(row) for _, row in close_only_sole8knot_sum_df.iterrows()]
num_subsidiary_close_only_sole8knot_sums = sum(subsidiary_close_only_sole8knot_sum_mask)

# 3.2
pendant_edges_both8knot_sum_mask = [has_pendant_edge_match(row) for _, row in edges_both8knot_sum_df.iterrows()] 
subsidiary_edges_both8knot_sum_mask = [has_subsidiary_edge_match(row) for _, row in edges_both8knot_sum_df.iterrows()] 
num_pendant_edges_both8knot_sums = sum(pendant_edges_both8knot_sum_mask)
num_subsidiary_edges_both8knot_sums = sum(subsidiary_edges_both8knot_sum_mask)

pendant_close_both8knot_sum_mask = [has_pendant_close_match(row) for _, row in close_both8knot_sum_df.iterrows()] 
subsidiary_close_both8knot_sum_mask = [has_subsidiary_close_match(row) for _, row in close_both8knot_sum_df.iterrows()]
num_pendant_close_both8knot_sums = sum(pendant_close_both8knot_sum_mask)
num_subsidiary_close_both8knot_sums = sum(subsidiary_close_both8knot_sum_mask)

# 3.3
pendant_edges_only_trailing8knot_sum_mask = [has_pendant_edge_match(row) for _, row in edges_only_trailing8knot_sum_df.iterrows()]
subsidiary_edges_only_trailing8knot_sum_mask = [has_subsidiary_edge_match(row) for _, row in edges_only_trailing8knot_sum_df.iterrows()]
num_pendant_edges_only_trailing8knot_sums = sum(pendant_edges_only_trailing8knot_sum_mask)
num_subsidiary_edges_only_trailing8knot_sums = sum(subsidiary_edges_only_trailing8knot_sum_mask)

pendant_close_only_trailing8knot_sum_mask = [has_pendant_close_match(row) for _, row in close_only_trailing8knot_sum_df.iterrows()] 
subsidiary_close_only_trailing8knot_sum_mask = [has_subsidiary_close_match(row) for _, row in close_only_trailing8knot_sum_df.iterrows()]
num_pendant_close_only_trailing8knot_sums = sum(pendant_close_only_trailing8knot_sum_mask)
num_subsidiary_close_only_trailing8knot_sums = sum(subsidiary_close_only_trailing8knot_sum_mask)

do_print = True
if do_print:
    print(f"ALL Figure-8 Knot Pendant Sums: {num_fig8knot_ascher_sums}")  
    print(f"# ONLY Sole-8-Knot Pendant Sums: {uloom.percent_info(num_only_sole8knot_ascher_sums, num_fig8knot_ascher_sums)}")
    print(f"\t# Sole-8-Knot Edge Match Pendant Sums: {uloom.percent_info(num_edges_only_sole8knot_sums, num_only_sole8knot_ascher_sums)}")
    print(f"\t\t# Pendant Sums: {uloom.percent_info(num_pendant_edges_only_sole8knot_sums, num_edges_only_sole8knot_sums)}")
    print(f"\t\t# Subsidiary Sums: {uloom.percent_info(num_subsidiary_edges_only_sole8knot_sums, num_edges_only_sole8knot_sums)}")
    print(f"\t# Sole-8-Knot Close Match Pendant Sums: {uloom.percent_info(num_close_only_sole8knot_sums, num_only_sole8knot_ascher_sums)}")
    print(f"\t\t# Pendant Sums: {uloom.percent_info(num_pendant_close_only_sole8knot_sums, num_close_only_sole8knot_sums)}")
    print(f"\t\t# Subsidiary Sums: {uloom.percent_info(num_subsidiary_close_only_sole8knot_sums, num_close_only_sole8knot_sums)}")
    
    print(f"# BOTH Sole-8-Knot and Trailing-8-Knot Pendant Sums: {uloom.percent_info(num_both8knot_ascher_sums, num_fig8knot_ascher_sums)}")
    print(f"\t# Both-8-Knot Edge Pendant Sums: {uloom.percent_info(num_edges_both8knot_sums, num_both8knot_ascher_sums)}")
    print(f"\t\t# Pendant Sums: {uloom.percent_info(num_pendant_edges_both8knot_sums, num_edges_both8knot_sums)}")
    print(f"\t\t# Subsidiary Sums: {uloom.percent_info(num_subsidiary_edges_both8knot_sums, num_edges_both8knot_sums)}")
    print(f"\t# Both-8-Knot Only Close Match Pendant Sums: {uloom.percent_info(num_close_both8knot_sums, num_both8knot_ascher_sums)}")
    print(f"\t\t# Pendant Sums: {uloom.percent_info(num_pendant_close_both8knot_sums, num_close_both8knot_sums)}")
    print(f"\t\t# Subsidiary Sums: {uloom.percent_info(num_subsidiary_close_both8knot_sums, num_close_both8knot_sums)}")
    
    print(f"# ONLY Trailing-8-Knot Pendant Pendant Sums: {uloom.percent_info(num_only_trailing8knot_ascher_sums, num_fig8knot_ascher_sums)}")
    print(f"\t# Trailing-8-Knot Edge Pendant Sums: {uloom.percent_info(num_edges_only_trailing8knot_edge_match_sums, num_only_trailing8knot_ascher_sums)}")
    print(f"\t\t# Pendant Sums: {uloom.percent_info(num_pendant_edges_only_trailing8knot_sums, num_edges_only_trailing8knot_edge_match_sums)}")
    print(f"\t\t# Subsidiary Sums: {uloom.percent_info(num_subsidiary_edges_only_trailing8knot_sums, num_edges_only_trailing8knot_edge_match_sums)}")
    print(f"\t# Trailing-8-Knot Only Close Pendant Ascher Sums: {uloom.percent_info(num_close_only_trailing8knot_edge_match_sums, num_only_trailing8knot_ascher_sums)}")
    print(f"\t\t# Pendant Sums: {uloom.percent_info(num_pendant_close_only_trailing8knot_sums, num_close_only_trailing8knot_edge_match_sums)}")
    print(f"\t\t# Subsidiary Sums: {uloom.percent_info(num_subsidiary_close_only_trailing8knot_sums, num_close_only_trailing8knot_edge_match_sums)}")
    print(f"# Other Eight-Knot Ascher Sums: {uloom.percent_info(num_fig8knot_ascher_sums - (num_only_sole8knot_ascher_sums + num_both8knot_ascher_sums + num_only_trailing8knot_ascher_sums), num_fig8knot_ascher_sums)}")
```
ALL Figure-8 Knot Pendant Sums: 5751
# ONLY Sole-8-Knot Pendant Sums: 48% (2753 of 5751)
    # Sole-8-Knot Edge Match Pendant Sums: 71% (1943 of 2753)
        # Pendant Sums: 72% (1405 of 1943)
        # Subsidiary Sums: 28% (538 of 1943)
    # Sole-8-Knot Close Match Pendant Sums: 29% (810 of 2753)
        # Pendant Sums: 62% (506 of 810)
        # Subsidiary Sums: 38% (304 of 810)
# BOTH Sole-8-Knot and Trailing-8-Knot Pendant Sums: 13% (762 of 5751)
    # Both-8-Knot Edge Pendant Sums: 88% (670 of 762)
        # Pendant Sums: 76% (507 of 670)
        # Subsidiary Sums: 24% (163 of 670)
    # Both-8-Knot Only Close Match Pendant Sums: 12% (92 of 762)
        # Pendant Sums: 89% (82 of 92)
        # Subsidiary Sums: 11% (10 of 92)
# ONLY Trailing-8-Knot Pendant Pendant Sums: 38% (2181 of 5751)
    # Trailing-8-Knot Edge Pendant Sums: 64% (1385 of 2181)
        # Pendant Sums: 91% (1265 of 1385)
        # Subsidiary Sums: 9% (120 of 1385)
    # Trailing-8-Knot Only Close Pendant Ascher Sums: 36% (796 of 2181)
        # Pendant Sums: 90% (719 of 796)
        # Subsidiary Sums: 10% (77 of 796)
# Other Eight-Knot Ascher Sums: 1% (55 of 5751)
Code
```{python}
# Code to generate R Sankey diagram source code
# See src/R_src/figure8knot_sumcords_sankey_2.R

do_make_sumcords_sankey_diagram = False
if do_make_sumcords_sankey_diagram:
    n_T = num_fig8knot_ascher_sums      
    n_A = num_only_sole8knot_ascher_sums
    n_B = num_both8knot_ascher_sums
    n_C = num_only_trailing8knot_ascher_sums
    n_D = num_fig8knot_ascher_sums - (num_only_sole8knot_ascher_sums + num_both8knot_ascher_sums + num_only_trailing8knot_ascher_sums)
    n_A1 = num_edges_only_sole8knot_sums
    n_A2 = num_close_only_sole8knot_sums
    n_A11 = num_pendant_edges_only_sole8knot_sums
    n_A12 = num_subsidiary_edges_only_sole8knot_sums    
    n_A21 = num_pendant_close_only_sole8knot_sums
    n_A22 = num_subsidiary_close_only_sole8knot_sums
    n_B1 = num_edges_both8knot_sums
    n_B2 = num_close_both8knot_sums
    n_B11 = num_pendant_edges_both8knot_sums
    n_B12 = num_subsidiary_edges_both8knot_sums
    n_B21 = num_pendant_close_both8knot_sums
    n_B22 = num_subsidiary_close_both8knot_sums
    n_C1 = num_edges_only_trailing8knot_edge_match_sums
    n_C2 = num_close_only_trailing8knot_edge_match_sums
    n_C11 = num_pendant_edges_only_trailing8knot_sums
    n_C12 = num_subsidiary_edges_only_trailing8knot_sums
    n_C21 = num_pendant_close_only_trailing8knot_sums
    n_C22 = num_subsidiary_close_only_trailing8knot_sums

    def pct(n, d): return int(round(100.0 * float(n) / float(d), 0))

    T = f"# of 8Knot Pendant Sums ( {n_T} )"
    A = f"( {pct(n_A, n_T)}% ) ONLY Sole-8-Knot Pendant Sums"
    B = f"( {pct(n_B, n_T)}% ) Sole AND Trailing-8-Knot Pendant Sums"
    C = f"( {pct(n_C, n_T)}% ) ONLY Trailing-8-Knot Pendant Sums"
    D = f"( {pct(n_D, n_T)}% ) Other 8-Knot Pendant Sums"
    A1 = f"( {pct(n_A1, n_A)}% ) ONLY Sole Edge 8Knot Markers"
    A2 = f"( {pct(n_A2, n_A)}% ) ONLY Sole Neighbor 8Knot Markers"
    A11 = f"( {pct(n_A11, n_A1)}% ) ONLY Sole-8-Knot Pendant Edge 8Knot Markers"
    A12 = f"( {pct(n_A12, n_A1)}% ) ONLY Sole-8-Knot Subsidiary Edge 8Knot Markers"
    A21 = f"( {pct(n_A21, n_A2)}% ) ONLY Sole-8-Knot Pendant Neighbor 8Knot Markers"
    A22 = f"( {pct(n_A22, n_A2)}% ) ONLY Sole-8-Knot Subsidiary Neighbor 8Knot Markers"
    B1 = f"( {pct(n_B1, n_B)}% ) Sole & Trailing Edge 8Knot Markers"
    B2 = f"( {pct(n_B2, n_B)}% ) Sole & Trailing Neighbor 8Knot Markers"
    B11 = f"( {pct(n_B11, n_B1)}% ) Sole & Trailing Pendant Edge 8Knot Markers"
    B12 = f"( {pct(n_B12, n_B1)}% ) Sole & Trailing Subsidiary Edge 8Knot Markers"
    B21 = f"( {pct(n_B21, n_B2)}% ) Sole & Trailing Pendant Neighbor 8Knot Markers"
    B22 = f"( {pct(n_B22, n_B2)}% ) Sole & Trailing Subsidiary Neighbor 8Knot Markers"
    C1 = f"( {pct(n_C1, n_C)}% ) ONLY Trailing Edge 8Knot Markers"
    C2 = f"( {pct(n_C2, n_C)}% ) ONLY Trailing Neighbor 8Knot Markers"
    C11 = f"( {pct(n_C11, n_C1)}% ) ONLY Trailing Pendant Edge 8Knot Markers"
    C12 = f"( {pct(n_C12, n_C1)}% ) ONLY Trailing Subsidiary Edge 8Knot Markers"
    C21 = f"( {pct(n_C21, n_C2)}% ) ONLY Trailing Pendant Neighbor 8Knot Markers"
    C22 = f"( {pct(n_C22, n_C2)}% ) ONLY Trailing Subsidiary Neighbor 8Knot Markers"


    print(f"\tsource = c(\"{T}\", \"{T}\", \"{T}\", \"{T}\",")
    print(f"\t\t\"{A}\", \"{A}\",")
    print(f"\t\t\"{A1}\", \"{A1}\", \"{A2}\", \"{A2}\",")
    print(f"\t\t\"{B}\", \"{B}\",")
    print(f"\t\t\"{B1}\", \"{B1}\", \"{B2}\", \"{B2}\",")
    print(f"\t\t\"{C}\", \"{C}\",")
    print(f"\t\t\"{C1}\", \"{C1}\", \"{C2}\", \"{C2}\"),")  
    print(f"\ttarget = c(\"{A}\", \"{B}\", \"{C}\", \"{D}\",")
    print(f"\t\t\"{A1}\", \"{A2}\",")
    print(f"\t\t\"{A11}\", \"{A12}\", \"{A21}\", \"{A22}\",")
    print(f"\t\t\"{B1}\", \"{B2}\",")   
    print(f"\t\t\"{B11}\", \"{B12}\", \"{B21}\", \"{B22}\",")
    print(f"\t\t\"{C1}\", \"{C2}\",")   
    print(f"\t\t\"{C11}\", \"{C12}\", \"{C21}\", \"{C22}\"),")
    print(f"\tvalue=c({n_A}, {n_B}, {n_C}, {n_D},") 
    print(f"\t\t{n_A1}, {n_A2},")   
    print(f"\t\t{n_A11}, {n_A12}, {n_A21}, {n_A22},")
    print(f"\t\t{n_B1}, {n_B2},")
    print(f"\t\t{n_B11}, {n_B12}, {n_B21}, {n_B22},")
    print(f"\t\t{n_C1}, {n_C2},")
    print(f"\t\t{n_C11}, {n_C12}, {n_C21}, {n_C22})")

    # Print color codes for nodes
    print("\n# Color codes for nodes:")
    print(f"\tname == \"{T}\" ~ \"{T}\",")
    print(f"\tname %in% c(\"{A}\",\"{B}\",\"{C}\",\"{D}\") ~ name,")
    print(f"\tname %in% c(\"{A1}\", \"{A2}\",\"{A11}\", \"{A12}\", \"{A21}\", \"{A22}\") ~ \"{A}\",")
    print(f"\tname %in% c(\"{B1}\", \"{B2}\",\"{B11}\", \"{B12}\", \"{B21}\", \"{B22}\") ~ \"{B}\",")
    print(f"\tname %in% c(\"{C1}\", \"{C2}\",\"{C11}\", \"{C12}\", \"{C21}\", \"{C22}\") ~ \"{C}\",")

    print(f"\nABCD = c(\"{A}\",\"{B}\",\"{C}\",\"{D}\")")

```

Finally we are ready to encapsulate this as Sankey diagrams:

4.1 Figure8Knot Sums by Percentage of Khipus

Click on image to view larger Figure8Knot Sums Sankey By Khipu

4.2 Figure8Knot Sums by Percentage of All Pendant Ascher Sums

Click on image to view larger Figure8Knot Sums Sankey By Khipu

5.0 - Diagrammatic Representations of the Ascher Summand Ranges by Figure-8 Knots

We can build a summand map of Ascher Sums marked by Figure8Knots for Left and Right-Handed Sums. For example here is a zoomed in snapshot of the summand map for Right-Handed Sums for KH0143 showing the relationships between Figure-8-Knots, their types, and the Ascher Summand regions.

An individual summand map for each khipu is available via its catalog page. For example here is the pendant-pendant sum drawings and information for KH0143