steffenc commited on
Commit
dea3205
·
1 Parent(s): 3304383

Working metagraph dashboard

Browse files
Files changed (4) hide show
  1. meta_plotting.py +119 -19
  2. meta_utils.py +33 -5
  3. metadash.py +152 -45
  4. multigraph.py +16 -2
meta_plotting.py CHANGED
@@ -1,31 +1,41 @@
1
  import numpy as np
 
2
  import plotly.express as px
 
3
 
4
- def plot_trace(df, col='emission', agg='mean', time_col='timestamp', ntop=10, hotkeys=None, hotkey_regex=None, abbrev=8, type='Miners'):
 
 
5
 
6
  if hotkeys is not None:
7
  df = df.loc[df.hotkey.isin(hotkeys)]
8
  if hotkey_regex is not None:
9
  df = df.loc[df.hotkey.str.contains(hotkey_regex)]
10
-
11
- top_miners = df.groupby('hotkey')[col].agg(agg).sort_values(ascending=False)
12
 
 
 
 
13
  stats = df.loc[df.hotkey.isin(top_miners.index[:ntop])].sort_values(by=time_col)
 
 
 
14
 
15
  stats['hotkey_abbrev'] = stats.hotkey.str[:abbrev]
16
  stats['coldkey_abbrev'] = stats.coldkey.str[:abbrev]
17
  stats['rank'] = stats.hotkey.map({k:i for i,k in enumerate(top_miners.index, start=1)})
 
18
 
 
19
  return px.line(stats.sort_values(by=[time_col,'rank']),
20
- x=time_col, y=col, color='coldkey_abbrev', line_group='hotkey_abbrev',
21
- hover_data=['hotkey','rank'],
22
- labels={col:col.title(),'timestamp':'','coldkey_abbrev':f'Coldkey (first {abbrev} chars)','hotkey_abbrev':f'Hotkey (first {abbrev} chars)'},
23
- title=f'Top {ntop} {type}, by {col.title()}',
24
- template='plotly_white', width=800, height=600,
25
- ).update_traces(opacity=0.7)
26
 
27
 
28
- def plot_cabals(df, sel_col='coldkey', count_col='hotkey', time_col='timestamp', values=None, ntop=10, abbr=8):
29
 
30
  if values is None:
31
  values = df[sel_col].value_counts().sort_values(ascending=False).index[:ntop].tolist()
@@ -33,16 +43,106 @@ def plot_cabals(df, sel_col='coldkey', count_col='hotkey', time_col='timestamp',
33
 
34
  df = df.loc[df[sel_col].isin(values)]
35
  rates = df.groupby([time_col,sel_col])[count_col].nunique().reset_index()
 
 
 
 
36
  abbr_col = f'{sel_col} (first {abbr} chars)'
37
  rates[abbr_col] = rates[sel_col].str[:abbr]
38
  return px.line(rates.melt(id_vars=[time_col,sel_col,abbr_col]),
39
- x=time_col, y='value', color=abbr_col,
40
- #facet_col='variable', facet_col_wrap=1,
41
- labels={'value':f'Number of Unique {count_col.title()}s per {sel_col.title()}','timestamp':''},
42
- category_orders={abbr_col:[ v[:abbr] for v in values]},
43
- # title=f'Unique {count_col.title()}s Associated with Selected {sel_col.title()}s in Metagraph',
44
- title=f'Impact of Validators Update on Cabal',
45
- width=800, height=600, template='plotly_white',
46
- )
47
 
48
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import numpy as np
2
+ import pandas as pd
3
  import plotly.express as px
4
+ from plotly.subplots import make_subplots
5
 
6
+ plotly_config = dict(width=800, height=600, template='plotly_white')
7
+
8
+ def plot_trace(df, col='emission', agg='mean', time_col='timestamp', ntop=10, hotkeys=None, hotkey_regex=None, abbrev=8, type='Miners', smooth=1, smooth_agg='mean', opacity=0.):
9
 
10
  if hotkeys is not None:
11
  df = df.loc[df.hotkey.isin(hotkeys)]
12
  if hotkey_regex is not None:
13
  df = df.loc[df.hotkey.str.contains(hotkey_regex)]
 
 
14
 
15
+ # select hotkeys with highest average value of col (e.g. emission) over time
16
+ top_miners = df.groupby('hotkey')[col].agg(agg).sort_values(ascending=False)
17
+ print(f'Top miners by {col!r}:\n{top_miners}')
18
  stats = df.loc[df.hotkey.isin(top_miners.index[:ntop])].sort_values(by=time_col)
19
+
20
+ # smooth values of col (e.g. emission) over time
21
+ # stats[col] = stats.groupby('hotkey')[col].rolling(smooth).agg(smooth_agg).values
22
 
23
  stats['hotkey_abbrev'] = stats.hotkey.str[:abbrev]
24
  stats['coldkey_abbrev'] = stats.coldkey.str[:abbrev]
25
  stats['rank'] = stats.hotkey.map({k:i for i,k in enumerate(top_miners.index, start=1)})
26
+ print(stats)
27
 
28
+ y_label = col.title().replace('_',' ') + f' ({agg})'
29
  return px.line(stats.sort_values(by=[time_col,'rank']),
30
+ x=time_col, y=col, color='coldkey_abbrev', line_group='hotkey_abbrev',
31
+ hover_data=['hotkey','rank'],
32
+ labels={col:y_label,'timestamp':'','coldkey_abbrev':f'Coldkey (first {abbrev} chars)','hotkey_abbrev':f'Hotkey (first {abbrev} chars)'},
33
+ title=f'Top {ntop} {type}, by {y_label}',
34
+ **plotly_config
35
+ ).update_traces(opacity=opacity)
36
 
37
 
38
+ def plot_cabals(df, sel_col='coldkey', count_col='hotkey', time_col='timestamp', values=None, ntop=10, abbr=8, smooth=1, smooth_agg='mean', opacity=0.):
39
 
40
  if values is None:
41
  values = df[sel_col].value_counts().sort_values(ascending=False).index[:ntop].tolist()
 
43
 
44
  df = df.loc[df[sel_col].isin(values)]
45
  rates = df.groupby([time_col,sel_col])[count_col].nunique().reset_index()
46
+
47
+ # smoothing is hard
48
+ # rates = rates.groupby(level=1).rolling(smooth, min_periods=1).agg(smooth_agg)
49
+
50
  abbr_col = f'{sel_col} (first {abbr} chars)'
51
  rates[abbr_col] = rates[sel_col].str[:abbr]
52
  return px.line(rates.melt(id_vars=[time_col,sel_col,abbr_col]),
53
+ x=time_col, y='value', color=abbr_col,
54
+ labels={'value':f'Number of Unique {count_col.title()}s per {sel_col.title()}','timestamp':''},
55
+ category_orders={abbr_col:[ v[:abbr] for v in values]},
56
+ title=f'Unique {count_col.title()}s Associated with Top {ntop} {sel_col.title()}s',
57
+ **plotly_config
58
+ ).update_traces(opacity=opacity)
 
 
59
 
60
+ def plot_churn(df, time_col='timestamp', type='changed', step=1, smooth=1, smooth_agg='mean', opacity=0.5):
61
+ """
62
+ Produces a plotly figure which shows number of changed hotkeys in each step
63
+ """
64
+
65
+ def churn(s):
66
+ results = [{'delta':np.nan}]
67
+ for i, idx in enumerate(s.index[1:]):
68
+
69
+ curr = s.loc[idx]
70
+ prev = s.iloc[i]
71
+ if type == 'changed':
72
+ delta = curr.symmetric_difference(prev)
73
+ elif type == 'added':
74
+ delta = curr.difference(prev)
75
+ elif type == 'removed':
76
+ delta = prev.difference(curr)
77
+ else:
78
+ raise ValueError(f'Unknown type {type!r}')
79
+
80
+ results.append({'delta': len(delta)})
81
+
82
+ return pd.DataFrame(results, index=s.index)
83
+
84
+ churn_frame = churn(df.iloc[::step].groupby(['block','timestamp']).hotkey.unique().apply(set))
85
+
86
+ return px.line(churn_frame.rolling(smooth, min_periods=1).agg(smooth_agg).reset_index(),
87
+ x=time_col, y='delta',
88
+ labels={'delta':f'Number of {type.title()} Hotkeys','timestamp':''},
89
+ hover_name='block',
90
+ **plotly_config
91
+ ).update_traces(opacity=opacity)
92
+
93
+
94
+ def plot_occupancy(df, time_col='timestamp',step=1, smooth=1, smooth_agg='mean', opacity=0.5):
95
+ """
96
+ Produces a plotly figure which shows number of unique hotkeys in each step
97
+ """
98
+ occupancy_frame = df.iloc[::step].assign(
99
+ Type=df.iloc[::step].validator_trust.apply(lambda x: 'Miner' if x==0 else 'Validator')
100
+ ).groupby(['Type','timestamp','block']).hotkey.nunique()
101
+
102
+ # make two plots, with a secondary y axis
103
+ fig = make_subplots(specs=[[{"secondary_y": True}]])
104
+ trace1 = px.line(occupancy_frame.loc['Miner'].rolling(smooth, min_periods=1).agg(smooth_agg).reset_index(),
105
+ x='timestamp',y='hotkey', hover_name='block')
106
+ trace2 = px.line(occupancy_frame.loc['Validator'].rolling(smooth, min_periods=1).agg(smooth_agg).reset_index(),
107
+ x='timestamp', y='hotkey', hover_name='block')
108
+
109
+ fig.add_trace(trace1.data[0])
110
+ fig.add_trace(trace2.update_traces(line_color='red').data[0], secondary_y=True, row=1,col=1)
111
+
112
+ fig.update_yaxes(title_text='Miner Hotkeys', secondary_y=False) # Customize primary y-axis title
113
+ fig.update_yaxes(title_text='Validator Hotkeys', secondary_y=True, tickfont=dict(color='red'), title=dict(font_color='red')) # Customize secondary y-axis title
114
+
115
+ return fig.update_layout( **plotly_config).update_traces(opacity=opacity)
116
+
117
+ def plot_animation(df, x='emission_sum', y='total_stake_sum', color='emission_mean', size='hotkey_nunique', step=10, opacity=0.5):
118
+
119
+ agg_dict = {}
120
+ for column_name in [x, y, color, size]:
121
+ column, agg_name = column_name.rsplit('_', 1)
122
+
123
+ if column not in agg_dict:
124
+ agg_dict[column] = [agg_name]
125
+ else:
126
+ agg_dict[column].append(agg_name)
127
+
128
+ # select every nth block
129
+ if step>1:
130
+ blocks_subset = df.block.unique()[::step]
131
+ df = df.loc[df.block.isin(blocks_subset)]
132
+
133
+ df_agg = df.groupby(['block','timestamp','coldkey']).agg({'hotkey':'nunique', 'ip':'nunique', **agg_dict})
134
+ df_agg.columns = ['_'.join(col).strip() for col in df_agg.columns]
135
+ print(df_agg.columns)
136
+
137
+ return px.scatter(df_agg.reset_index(),
138
+ x=x, range_x=[-df_agg[x].max()*0.1, df_agg[x].max()*1.1],
139
+ y=y, range_y=[-df_agg[y].max()*0.1, df_agg[y].max()*1.1],
140
+ size=size,
141
+ opacity=opacity,
142
+ color=color,
143
+ color_continuous_scale='BlueRed',
144
+ animation_frame='block',
145
+ animation_group='coldkey',
146
+ hover_name='coldkey',
147
+ **plotly_config
148
+ )
meta_utils.py CHANGED
@@ -1,4 +1,5 @@
1
  import os
 
2
  import glob
3
  import tqdm
4
  import dill as pickle
@@ -11,10 +12,37 @@ block_time_500k = datetime.datetime(2023, 5, 29, 5, 29, 0)
11
  block_time_800k = datetime.datetime(2023, 7, 9, 21, 32, 48)
12
  dt = (pd.Timestamp(block_time_800k)-pd.Timestamp(block_time_500k))/(800_000-500_000)
13
 
14
- def run_subprocess(*args):
15
- # Trigger the multigraph.py script to run and save metagraph snapshots
16
- return subprocess.run('python multigraph.py'.split()+list(args),
17
- shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
  def load_metagraph(path, extra_cols=None, rm_cols=None):
20
 
@@ -24,7 +52,7 @@ def load_metagraph(path, extra_cols=None, rm_cols=None):
24
  df = pd.DataFrame(metagraph.axons)
25
  df['block'] = metagraph.block.item()
26
  df['timestamp'] = block_time_500k + dt*(df['block']-500_000)
27
- df['difficulty'] = metagraph.difficulty
28
  for c in extra_cols:
29
  vals = getattr(metagraph,c)
30
  df[c] = vals
 
1
  import os
2
+ import re
3
  import glob
4
  import tqdm
5
  import dill as pickle
 
12
  block_time_800k = datetime.datetime(2023, 7, 9, 21, 32, 48)
13
  dt = (pd.Timestamp(block_time_800k)-pd.Timestamp(block_time_500k))/(800_000-500_000)
14
 
15
+ def run_subprocess(command='python multigraph.py', *args):
16
+ try:
17
+ # Run the subprocess with stdout and stderr pipes connected
18
+ command = command + " ".join(args)
19
+ print(f'{"===="*20}\nRunning: {command!r}')
20
+ process = subprocess.Popen(
21
+ command,
22
+ stdout=subprocess.PIPE,
23
+ stderr=subprocess.STDOUT,
24
+ universal_newlines=True, # Set to True for text mode
25
+ bufsize=1, # Line buffered, so output is available line by line
26
+ shell=True # Set to True to allow running shell commands (use with caution)
27
+ )
28
+
29
+ print(f'Subprocess started with pid {process.pid} and streaming output from stdout:')
30
+ with process.stdout as output:
31
+ for line in output:
32
+ print(line, end='', flush=True) # Print without adding an extra newline
33
+ if match := re.search('(?P<done>\\d+)/(?P<total>\\d+)',line):
34
+ print('---> match.groupdict():', match.groupdict())
35
+ # try yielding the line here
36
+
37
+ # Wait for the subprocess to finish and get the return code
38
+ process.wait()
39
+
40
+ print("===="*20)
41
+ return process.returncode
42
+
43
+ except subprocess.CalledProcessError as e:
44
+ # If the subprocess returns a non-zero exit code, this exception will be raised
45
+ return e.returncode
46
 
47
  def load_metagraph(path, extra_cols=None, rm_cols=None):
48
 
 
52
  df = pd.DataFrame(metagraph.axons)
53
  df['block'] = metagraph.block.item()
54
  df['timestamp'] = block_time_500k + dt*(df['block']-500_000)
55
+ df['difficulty'] = getattr(metagraph, 'difficulty', None)
56
  for c in extra_cols:
57
  vals = getattr(metagraph,c)
58
  df[c] = vals
metadash.py CHANGED
@@ -1,9 +1,10 @@
1
  import os
 
2
  import pandas as pd
3
  import streamlit as st
4
  from meta_utils import run_subprocess, load_metagraphs
5
  # from opendashboards.assets import io, inspect, metric, plot
6
- from meta_plotting import plot_trace, plot_cabals
7
  import asyncio
8
  from functools import lru_cache
9
 
@@ -22,8 +23,9 @@ loop = asyncio.new_event_loop()
22
  asyncio.set_event_loop(loop)
23
  import bittensor
24
 
25
- datadir='data/metagraph/1/'
26
- blockfiles = sorted(int(filename.split('.')[0]) for filename in os.listdir(datadir))
 
27
  DEFAULT_SRC = 'miner'
28
  DEFAULT_BLOCK_START = blockfiles[0]
29
  DEFAULT_BLOCK_END = blockfiles[-1]
@@ -48,27 +50,29 @@ st.title('Metagraph :red[Analysis] Dashboard :eyes:')
48
  st.markdown('#')
49
  st.markdown('#')
50
 
51
- netuid = 1
52
  subtensor = bittensor.subtensor(network='finney')
53
  current_block = subtensor.get_current_block()
54
 
55
- @lru_cache(maxsize=1)
56
- def _metagraph(block):
57
 
58
- print('rerunning cache')
59
  return (
60
- subtensor.metagraph(netuid, block=block),
61
- subtensor.metagraph(netuid, block=block - 7200),
62
- subtensor.burn(netuid=netuid, block=block),
63
- subtensor.burn(netuid=netuid, block=block - 7200),
64
  )
65
-
66
- current_metagraph, yesterday_metagraph, current_burn, yesterday_burn = _metagraph(10*current_block//10)
67
 
68
- current_vcount = current_metagraph.validator_permit.sum().item()
69
- current_mcount = (~current_metagraph.validator_permit).sum().item()
70
- yesterday_vcount = yesterday_metagraph.validator_permit.sum().item()
71
- yesterday_mcount = (~yesterday_metagraph.validator_permit).sum().item()
 
 
 
 
 
72
 
73
  mcol1, mcol2, mcol3, mcol4 = st.columns(4)
74
  mcol1.metric('Block', current_block, delta='+7200 [24hr]')
@@ -77,30 +81,64 @@ mcol3.metric('Validators', current_vcount, delta=current_vcount-yesterday_vcount
77
  mcol4.metric('Miners', current_mcount, delta=current_mcount-yesterday_mcount)
78
 
79
 
80
- st.markdown('#')
81
  st.markdown('#')
82
 
83
- bcol1, bcol2, bcol3 = st.columns([0.6, 0.1, 0.2])
84
- block_start, block_end = bcol1.select_slider(
85
- 'Select a **block range**',
86
- options=blockfiles,
87
- value=(DEFAULT_BLOCK_START, DEFAULT_BLOCK_END),
88
- format_func=lambda x: f'{x:,}'
89
- )
90
 
91
- with bcol3:
 
92
  st.markdown('#')
93
- st.button('Refresh', on_click=run_subprocess)
 
94
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
 
96
  with st.spinner(text=f'Loading data...'):
97
  # df = load_metagraphs(block_start=block_start, block_end=block_end, block_step=DEFAULT_BLOCK_STEP)
98
- df = pd.read_parquet('blocks_600100_807300_100')
99
 
100
  blocks = df.block.unique()
101
 
102
- df_sel = df.loc[df.block.between(block_start, block_end)]
103
-
 
104
 
105
  # add vertical space
106
  st.markdown('#')
@@ -108,33 +146,102 @@ st.markdown('#')
108
 
109
  tab1, tab2, tab3, tab4 = st.tabs(["Overview", "Miners", "Validators", "Block"])
110
 
111
- miner_choices = ['total_stake','ranks','incentive','emission','consensus','trust','validator_trust','dividends']
 
112
  cabal_choices = ['hotkey','ip','coldkey']
 
113
 
114
  ### Overview ###
115
  with tab1:
116
 
117
- x_col = st.radio('X-axis', ['block','timestamp'], index=0, horizontal=True)
 
 
 
118
 
119
- acol1, acol2 = st.columns([0.3, 0.7])
120
- sel_ntop = acol1.slider('Number:', min_value=1, max_value=50, value=10, key='sel_ntop')
121
- #horizontal list
122
- miner_choice = acol2.radio('Select:', miner_choices, horizontal=True, index=0)
123
  st.plotly_chart(
124
- plot_trace(df_sel, time_col=x_col,col=miner_choice, ntop=sel_ntop),
125
  use_container_width=True
126
  )
127
-
128
- col1, col2 = st.columns(2)
129
- count_col = col1.radio('Count', cabal_choices, index=0, horizontal=True)
130
- y_col = col2.radio('Agg on', cabal_choices, index=2, horizontal=True)
131
-
132
  st.plotly_chart(
133
- plot_cabals(df_sel, time_col=x_col, count_col=count_col, sel_col=y_col, ntop=sel_ntop),
134
  use_container_width=True
135
  )
136
 
 
 
 
 
137
  with tab2:
138
 
139
- # plot of miner weights versus time/block
140
- pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
+ import time
3
  import pandas as pd
4
  import streamlit as st
5
  from meta_utils import run_subprocess, load_metagraphs
6
  # from opendashboards.assets import io, inspect, metric, plot
7
+ import meta_plotting as plotting
8
  import asyncio
9
  from functools import lru_cache
10
 
 
23
  asyncio.set_event_loop(loop)
24
  import bittensor
25
 
26
+ netuid = 1
27
+ datadir=f'data/metagraph/{netuid}/'
28
+ blockfiles = sorted(int(filename.split('.')[0]) for filename in os.listdir(datadir) if filename.split('.')[0].isdigit())
29
  DEFAULT_SRC = 'miner'
30
  DEFAULT_BLOCK_START = blockfiles[0]
31
  DEFAULT_BLOCK_END = blockfiles[-1]
 
50
  st.markdown('#')
51
  st.markdown('#')
52
 
 
53
  subtensor = bittensor.subtensor(network='finney')
54
  current_block = subtensor.get_current_block()
55
 
56
+ @st.cache_data
57
+ def _metagraph(block, subnet):
58
 
59
+ print(f'rerunning cache with block {block}')
60
  return (
61
+ subtensor.metagraph(subnet, block=block),
62
+ subtensor.metagraph(subnet, block=block - 7200),
63
+ subtensor.burn(netuid=subnet, block=block),
64
+ subtensor.burn(netuid=subnet, block=block - 7200),
65
  )
 
 
66
 
67
+ current_metagraph, yesterday_metagraph, current_burn, yesterday_burn = _metagraph(10*(current_block//10), netuid)
68
+ current_validators = current_metagraph.validator_permit[current_metagraph.validator_trust > 0.0]
69
+ yesterday_validators = yesterday_metagraph.validator_permit[yesterday_metagraph.validator_trust > 0.0]
70
+ current_vcount = current_validators.sum().item()
71
+ current_mcount = (current_metagraph.trust > 0.0).sum().item()
72
+ yesterday_vcount = yesterday_validators.sum().item()
73
+ yesterday_mcount = (yesterday_metagraph.trust > 0.0).sum().item()
74
+
75
+ st.markdown('#')
76
 
77
  mcol1, mcol2, mcol3, mcol4 = st.columns(4)
78
  mcol1.metric('Block', current_block, delta='+7200 [24hr]')
 
81
  mcol4.metric('Miners', current_mcount, delta=current_mcount-yesterday_mcount)
82
 
83
 
 
84
  st.markdown('#')
85
 
 
 
 
 
 
 
 
86
 
87
+ with st.sidebar:
88
+ st.title('Options')
89
  st.markdown('#')
90
+
91
+ netuid = st.selectbox('Netuid', [1,11], index=0)
92
 
93
+ st.markdown('#')
94
+
95
+ c1, c2 = st.columns([0.7,0.3])
96
+ staleness = current_block - blockfiles[-1]
97
+ msg = c1.warning(f'Out of date ({staleness})') if staleness >= 100 else c1.info('Up to date')
98
+ if c2.button('Update', type='primary'):
99
+ msg.info('Downloading')
100
+ return_code = run_subprocess()
101
+ if return_code == 0:
102
+ msg.success('Up to date')
103
+ time.sleep(1)
104
+ msg.empty()
105
+ else:
106
+ msg.error('Error')
107
+
108
+ st.markdown('#')
109
+
110
+ block_start, block_end = st.select_slider(
111
+ 'Select a **block range**',
112
+ options=blockfiles,
113
+ value=(DEFAULT_BLOCK_START, DEFAULT_BLOCK_END),
114
+ format_func=lambda x: f'{x:,}'
115
+ )
116
+
117
+ st.markdown('#')
118
+ st.markdown('#')
119
+ # horizontal line
120
+ st.markdown('<hr>', unsafe_allow_html=True)
121
+
122
+ r1c1, r1c2 = st.columns(2)
123
+ x = r1c1.selectbox('**Time axis**', ['block','timestamp'], index=0)
124
+ color = r1c2.selectbox('**Color**', ['coldkey','hotkey','ip'], index=0)
125
+ r2c1, r2c2 = st.columns(2)
126
+ ntop = r2c1.slider('**Sample top**', min_value=1, max_value=50, value=10, key='sel_ntop')
127
+ opacity = r2c2.slider('**Opacity**', min_value=0., max_value=1., value=0.5, key='opacity')
128
+ r3c1, r3c2 = st.columns(2)
129
+ smooth = r3c1.slider('Smoothing', min_value=1, max_value=100, value=1, key='sel_churn_smooth')
130
+ smooth_agg = r3c2.radio('Smooth Aggregation', ['mean','std','max','min','sum'], horizontal=True, index=0)
131
+
132
 
133
  with st.spinner(text=f'Loading data...'):
134
  # df = load_metagraphs(block_start=block_start, block_end=block_end, block_step=DEFAULT_BLOCK_STEP)
135
+ df = pd.read_parquet(os.path.join(datadir,'df.parquet'))
136
 
137
  blocks = df.block.unique()
138
 
139
+ df_sel = df.loc[df.block.between(block_start, block_end)].sort_values(by='block')
140
+ miners = df_sel.loc[df_sel.validator_trust == 0]
141
+ validators = df_sel.loc[df_sel.validator_trust > 0]
142
 
143
  # add vertical space
144
  st.markdown('#')
 
146
 
147
  tab1, tab2, tab3, tab4 = st.tabs(["Overview", "Miners", "Validators", "Block"])
148
 
149
+ validator_choices = ['total_stake','incentive','emission','consensus','validator_trust','dividends']
150
+ miner_choices = ['total_stake','incentive','emission','consensus','trust']
151
  cabal_choices = ['hotkey','ip','coldkey']
152
+ cabal_choices.remove(color)
153
 
154
  ### Overview ###
155
  with tab1:
156
 
157
+ st.markdown('#')
158
+ st.markdown('#')
159
+ st.subheader('Hotkey Churn')
160
+ st.info('**Churn** *measures the change in network participants over time*')
161
 
162
+ churn_choice = st.radio('Hotkey event', ['changed','added','removed'], horizontal=True, index=0)
163
+
 
 
164
  st.plotly_chart(
165
+ plotting.plot_churn(df_sel, time_col=x, type=churn_choice, smooth=smooth, smooth_agg=smooth_agg, opacity=opacity),
166
  use_container_width=True
167
  )
168
+
169
+ st.markdown('#')
170
+ st.markdown('#')
171
+ st.subheader('Network Occupancy')
172
+ st.info('**Occupancy** *measures the number of network participants at a given time*')
173
  st.plotly_chart(
174
+ plotting.plot_occupancy(df_sel, time_col=x, smooth=smooth, smooth_agg=smooth_agg, opacity=opacity),
175
  use_container_width=True
176
  )
177
 
178
+ animation_aggs = ['mean','sum','std','max','min']
179
+ mac_choices = [f'{col}_{agg}' for col in miner_choices for agg in animation_aggs]+['hotkey_nunique','ip_nunique']
180
+ vac_choices = [f'{col}_{agg}' for col in validator_choices for agg in animation_aggs]+['hotkey_nunique','ip_nunique']
181
+
182
  with tab2:
183
 
184
+ st.markdown('#')
185
+ st.markdown('#')
186
+ st.subheader('Miner Activity')
187
+ st.info('**Activity** *shows the change in stake and emission over time for **miners**, grouped by coldkey*')
188
+
189
+ mac1, mac2, mac3, mac4 = st.columns(4)
190
+ mac_x = mac1.selectbox('**x**', mac_choices, index=mac_choices.index('emission_mean'))
191
+ mac_y = mac2.selectbox('**y**', mac_choices, index=mac_choices.index('trust_mean'))
192
+ mac_size = mac3.selectbox('**marker size**', mac_choices, index=mac_choices.index('hotkey_nunique'))
193
+ mac_color = mac4.selectbox('**marker color**', mac_choices, index=mac_choices.index('total_stake_sum'))
194
+
195
+ st.plotly_chart(
196
+ plotting.plot_animation(miners, x=mac_x, y=mac_y, color=mac_color, size=mac_size, opacity=opacity),
197
+ use_container_width=True
198
+ )
199
+
200
+ miner_choice = st.radio('Select:', miner_choices, horizontal=True, index=0)
201
+ with st.expander(f'Show **{miner_choice}** trends for top **{ntop}** miners'):
202
+
203
+ st.plotly_chart(
204
+ plotting.plot_trace(miners, time_col=x, col=miner_choice, ntop=ntop, smooth=smooth, smooth_agg=smooth_agg, opacity=opacity),
205
+ use_container_width=True
206
+ )
207
+
208
+ count_col = st.radio('Count', cabal_choices, index=0, horizontal=True, key='sel_miner_count')
209
+ with st.expander(f'Show **{count_col}** trends for top **{ntop}** miners'):
210
+
211
+ st.plotly_chart(
212
+ plotting.plot_cabals(miners, time_col=x, count_col=count_col, sel_col=color, ntop=ntop, smooth=smooth, smooth_agg=smooth_agg, opacity=opacity),
213
+ use_container_width=True
214
+ )
215
+
216
+ with tab3:
217
+
218
+ st.markdown('#')
219
+ st.markdown('#')
220
+ st.subheader('Validator Activity')
221
+ st.info('**Activity** *shows the change in stake and emission over time for **validators**, grouped by coldkey*')
222
+
223
+ vac1, vac2, vac3, vac4 = st.columns(4)
224
+ vac_x = vac1.selectbox('**x**', vac_choices, index=vac_choices.index('incentive_mean'))
225
+ vac_y = vac2.selectbox('**y**', vac_choices, index=vac_choices.index('validator_trust_mean'))
226
+ vac_size = vac3.selectbox('**marker size**', vac_choices, index=vac_choices.index('hotkey_nunique'))
227
+ vac_color = vac4.selectbox('**marker color**', vac_choices, index=vac_choices.index('total_stake_sum'))
228
+
229
+ st.plotly_chart(
230
+ plotting.plot_animation(validators, x=vac_x, y=vac_y, color=vac_color, size=vac_size, opacity=opacity),
231
+ use_container_width=True
232
+ )
233
+ validator_choice = st.radio('Select:', validator_choices, horizontal=True, index=0)
234
+ with st.expander(f'Show **{validator_choice}** trends for top **{ntop}** validators'):
235
+
236
+ st.plotly_chart(
237
+ plotting.plot_trace(validators, time_col=x,col=validator_choice, ntop=ntop, smooth=smooth, smooth_agg=smooth_agg, opacity=opacity),
238
+ use_container_width=True
239
+ )
240
+
241
+ count_col = st.radio('Count', cabal_choices, index=0, horizontal=True, key='sel_validator_count')
242
+ with st.expander(f'Show **{count_col}** trends for top **{ntop}** validators'):
243
+
244
+ st.plotly_chart(
245
+ plotting.plot_cabals(validators, time_col=x, count_col=count_col, sel_col=color, ntop=ntop, smooth=smooth, smooth_agg=smooth_agg, opacity=opacity),
246
+ use_container_width=True
247
+ )
multigraph.py CHANGED
@@ -115,5 +115,19 @@ if __name__ == '__main__':
115
 
116
 
117
  if not args.no_dataframe:
118
- df = load_metagraphs(min(blocks), max(blocks), block_step=step_size, datadir=datadir)
119
- df.to_pickle(f'data/metagraph/{netuid}/df.pkl')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
 
116
 
117
  if not args.no_dataframe:
118
+ save_path = f'data/metagraph/{netuid}/df.parquet'
119
+ blocks = range(start_block, end_block, step_size)
120
+ df_loaded = None
121
+ if os.path.exists(save_path):
122
+ df_loaded = pd.read_parquet(save_path)
123
+ blocks = [block for block in blocks if block not in df_loaded.block.unique()]
124
+ print(f'Loaded dataframe from {save_path!r}. {len(df_loaded)} rows. {len(blocks)} blocks to process.')
125
+ if len(blocks)==0:
126
+ print('No blocks to process.')
127
+ sys.exit(0)
128
+
129
+ df = load_metagraphs(blocks[0], blocks[-1], block_step=step_size, datadir=datadir)
130
+ if df_loaded is not None:
131
+ df = pd.concat([df, df_loaded], ignore_index=True)
132
+ df.to_parquet(save_path)
133
+ print(f'Saved dataframe to {save_path!r}')