<jupyter_start><jupyter_text>Natural Partitioning using 3-4-5 Rule<jupyter_code>import numpy as np
import pandas as pd
from math import log10 , log , floor<jupyter_output><empty_output><jupyter_text>Applying on Give Data
(1st variant, directly printing the tree) Divide Function finds number of unique values at most significant digit, and accordingly applies 3-4-5 rule<jupyter_code>def divide(dataOrignal,depth):
  if depth != 0:
    data = dataOrignal
    msd = 10**floor(log10(max(dataOrignal)))
    data = np.array([i//msd for i in data])
    n = len(np.unique(data))
    print('\t'*(2-depth),n,"Distinct MSD")
    if n in [3,6,9]:
      print('\t'*(2-depth),"3 Partition")
      partition(dataOrignal,depth,3)
    elif n in [2,4,8]:
      print('\t'*(2-depth),"4 Partition")
      partition(dataOrignal,depth,4)
    elif n in [1,5,10]:
      print('\t'*(2-depth),"5 Partition")
      partition(dataOrignal,depth,5)<jupyter_output><empty_output><jupyter_text>Partition accepts depth and n, where n is number of groups to partition data into.

Then, partition recursively calls divide on each group of data with depth = depth-1

When depth is 0, the process stops.<jupyter_code>def partition(arr,depth,n):
  if depth!=0:
    m = min(arr)
    w = (max(arr)-m)/n
    d = {}
    for i in range(n):
      d[i] = []
    for num in arr:
      for i in range(n):
        if num < m + w*(i+1):
          if not i in d.keys():
            d[i] = []
          d[i].append(num)  
          break  
    for i,v in d.items():
      print('\t'*(2-depth),m+i*w,'to',m+(i+1)*w)
      print('\t'*(2-depth),v)
      if len(v)==0:continue
      #print('i=',depth)
      divide(v,depth-1)<jupyter_output><empty_output><jupyter_text>**Printing Output**<jupyter_code>dataOrignal = np.array([32, 38, 48, 91, 46, 37, 22, 69, 78, 82, 33, 49, 55, 66, 84, 86, 67, 80, 79, 44])
divide(dataOrignal,2)<jupyter_output>8 Distinct MSD
 4 Partition
 22.0 to 39.25
 [32, 38, 37, 22, 33]
	 2 Distinct MSD
	 4 Partition
	 22.0 to 26.0
	 [22]
	 26.0 to 30.0
	 []
	 30.0 to 34.0
	 [32, 33]
	 34.0 to 38.0
	 [37]
 39.25 to 56.5
 [48, 46, 49, 55, 44]
	 2 Distinct MSD
	 4 Partition
	 44.0 to 46.75
	 [46, 44]
	 46.75 to 49.5
	 [48, 49]
	 49.5 to 52.25
	 []
	 52.25 to 55.0
	 []
 56.5 to 73.75
 [69, 66, 67]
	 1 Distinct MSD
	 5 Partition
	 66.0 to 66.6
	 [66]
	 66.6 to 67.2
	 [67]
	 67.2 to 67.8
	 []
	 67.8 to 68.4
	 []
	 68.4 to 69.0
	 []
 73.75 to 91.0
 [78, 82, 84, 86, 80, 79]
	 2 Distinct MSD
	 4 Partition
	 78.0 to 80.0
	 [78, 79]
	 80.0 to 82.0
	 [80]
	 82.0 to 84.0
	 [82]
	 84.0 to 86.0
	 [84]<jupyter_text>Real-World Example

Second Variant : Storing The Tree In Dictionary<jupyter_code>star = pd.read_csv('star.csv')
temperatures = star[star.columns[0]]
temperatures
divide(temperatures,2)
def partition1(arr,depth,n):
  if depth!=0:
    m = min(arr)
    w = (max(arr)-m)/n
    d,dd = {},{}
    for i in range(n):
      d[i] = []
      dd[i]= []
    for num in arr:
      for i in range(n):
        if num < m + w*(i+1):
          if not i in d.keys():
            d[i] = []
          d[i].append(num)  
          break  
    for i,v in d.items():
      #print('\t'*(2-depth),m+i*w,'to',m+(i+1)*w)
      #print('\t'*(2-depth),v)
      if len(v)==0:continue
      #print('i=',depth)
      dd[i]=(m+i*w,m+(i+1)*w,divide1(v,depth-1))
    return dd
  return arr
def divide1(dataOrignal,depth):
  if depth != 0:
    data = dataOrignal
    msd = 10**floor(log10(max(dataOrignal)))
    data = np.array([i//msd for i in data])
    n = len(np.unique(data))
    #print('\t'*(2-depth),n,"Distinct MSD")
    if n in [3,6,9]:
      #print('\t'*(2-depth),"3 Partition")
      return partition1(dataOrignal,depth,3)
    elif n in [2,4,8]:
      #print('\t'*(2-depth),"4 Partition")
      return partition1(dataOrignal,depth,4)
    elif n in [1,5,10]:
      #print('\t'*(2-depth),"5 Partition")
      return partition1(dataOrignal,depth,5)
  return dataOrignal

treeDict = divide1(temperatures,2)
def printTreeDict(td,d):
  if d==2 : return
  for k,v in td.items():
    if len(v)==3:
      a,b,c = v
      print('\t'*d,a,'-',b)
      printTreeDict(c,d+1)
printTreeDict(treeDict,0)
print(treeDict)
def convert(d):  
  if not isinstance(d,dict):
    return str([])
  ans = {}
  for k,v in d.items():
    if isinstance(v,list):
      return str([])
    else:
      (a,b,c) = v
      if isinstance(c,list):
        
        if len(c) in range (0,10):
          ans[str(a)+" - "+str(b)] = str(c)   
        else:
            aa = c[:3]          
            aa.append(" ... ")
            aa.extend(c[-3:])
            ans[str(a)+" - "+str(b)] = str(aa)   
      else :
        ans[str(a)+" - "+str(b)]=convert(c)
  return ans
b={str(min(temperatures))+"-"+str(max(temperatures)):convert(treeDict)}
print(b)<jupyter_output>{'1939-40000': {'1939.0 - 9551.2': {'1939.0 - 4420.333333333334': "[3068, 3042, 2600, ' ... ', 3496, 3324, 3463]", '4420.333333333334 - 6901.666666666667': "[5800, 6757, 6380, ' ... ', 5112, 6850, 5752]", '6901.666666666667 - 9383.0': "[7740, 7220, 8500, ' ... ', 8250, 8829, 9235]"}, '9551.2 - 17163.4': {'9675.0 - 11541.25': "[9700, 10574, 9675, ' ... ', 10012, 9892, 10930]", '11541.25 - 13407.5': "[12990, 11790, 12010, ' ... ', 13340, 13089, 12749]", '13407.5 - 15273.75': '[14100, 13720, 14245, 13420, 14520, 14732, 14060, 14982]', '15273.75 - 17140.0': '[16500, 15276, 17120, 16390, 16790, 15680, 16787]'}, '17163.4 - 24775.6': {'17200.0 - 19057.5': '[17200, 18290, 17383, 18000, 18340, 17920, 18734]', '19057.5 - 20915.0': '[19860, 20120, 19923, 19920, 19360, 19400]', '20915.0 - 22772.5': '[21020, 22350, 22012, 21738, 21904]', '22772.5 - 24630.0': '[23000, 24345, 24490, 24020, 23092, 23678, 23440, 23095, 24145]'}, '24775.6 - 32387.8': {'25000.0 - 26459.75': '[25000, 25070, 26140, 25390, [...]<jupyter_text>Ploting Tree From My Dictionary
https://stackoverflow.com/a/13690321/12907462<jupyter_code>##https://stackoverflow.com/a/13690321/12907462
import pydot
def draw(parent_name, child_name):
    edge = pydot.Edge(parent_name, child_name)
    graph.add_edge(edge)

def visit(node, parent=None):
    for k,v in node.items():
        if isinstance(v, dict):
            # We start with the root node whose parent is None
            # we don't want to graph the None node
            if parent:
                draw(parent, k)
            visit(v, k)
        else:
            draw(parent, k)
            # drawing the label using a distinct name
            draw(k, v)
#############

graph = pydot.Dot(graph_type='graph')
visit(b)
graph.write_png('tree.png')

import cv2 
from google.colab.patches import cv2_imshow
image = cv2.imread('tree.png') 
cv2_imshow( image)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.17710013194546254, 'fraction_numerical': 0.14411376630992523, 'mean_word_length': 2.6038034865293187, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 23, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 4, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '6', 'dir': 'jupyter-structured-clean-dedup', 'id': '426884', 'license': 'MIT', 'n_tokens_mistral': 3439, 'n_tokens_neox': 2693, 'n_words': 655, 'path': 'Main.ipynb', 'repo_name': 'Nirav-Madhani/NaturalPartitioning', 'size': '6821'}