Spaces:
Sleeping
Sleeping
feat: replace pyvis with plotly for reliable graph rendering in Gradio
Browse files- app.py +179 -108
- requirements.txt +2 -1
app.py
CHANGED
|
@@ -1,13 +1,9 @@
|
|
| 1 |
import gradio as gr
|
| 2 |
import pandas as pd
|
| 3 |
import networkx as nx
|
| 4 |
-
|
| 5 |
from huggingface_hub import hf_hub_download
|
| 6 |
-
import
|
| 7 |
-
|
| 8 |
-
# ---------------------------------------------------------------------------
|
| 9 |
-
# Data loading (cached at module level so it happens once on Space boot)
|
| 10 |
-
# ---------------------------------------------------------------------------
|
| 11 |
|
| 12 |
REPO_ID = "overthelex/ua-court-citation-graph"
|
| 13 |
|
|
@@ -27,20 +23,20 @@ edges_df["node_b"] = edges_df["law_b"] + " ст. " + edges_df["article_b"]
|
|
| 27 |
ALL_LAWS = sorted(edges_df["law_a"].unique().tolist())
|
| 28 |
|
| 29 |
DOMAIN_COLORS = {
|
| 30 |
-
"Кримінальний кодекс України": "#
|
| 31 |
-
"Кримінальний процесуальний кодекс України": "#
|
| 32 |
-
"Цивільний кодекс України": "#
|
| 33 |
-
"Цивільний процесуальний кодекс України": "#
|
| 34 |
-
"Господарський кодекс України": "#
|
| 35 |
-
"Господарський процесуальний кодекс України": "#
|
| 36 |
-
"Кодекс адміністративного судочинства України": "#
|
| 37 |
-
"КУпАП": "#
|
| 38 |
-
"Податковий кодекс України": "#
|
| 39 |
-
"Сімейний кодекс України": "#
|
| 40 |
-
"Земельний кодекс України": "#
|
| 41 |
-
"Конституція України": "#
|
| 42 |
}
|
| 43 |
-
DEFAULT_COLOR = "#
|
| 44 |
|
| 45 |
print(f"Loaded {len(edges_df):,} edges, {len(centrality_df)} centrality nodes, {len(stats_df):,} citation stats")
|
| 46 |
|
|
@@ -52,12 +48,31 @@ def get_color(law_name: str) -> str:
|
|
| 52 |
return DEFAULT_COLOR
|
| 53 |
|
| 54 |
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
filtered = edges_df[edges_df["weight"] >= min_weight]
|
| 62 |
|
| 63 |
if law_filter:
|
|
@@ -67,7 +82,16 @@ def build_graph(
|
|
| 67 |
filtered = filtered.nlargest(max_edges, "weight")
|
| 68 |
|
| 69 |
if filtered.empty:
|
| 70 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
|
| 72 |
G = nx.Graph()
|
| 73 |
for _, row in filtered.iterrows():
|
|
@@ -76,78 +100,143 @@ def build_graph(
|
|
| 76 |
degree_dict = dict(G.degree(weight="weight"))
|
| 77 |
pr = nx.pagerank(G, weight="weight")
|
| 78 |
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
)
|
| 88 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
max_pr = max(pr.values()) if pr else 1
|
|
|
|
| 90 |
for node in G.nodes():
|
| 91 |
parts = node.split(" ст. ", 1)
|
| 92 |
-
law = parts[0] if len(parts) == 2 else ""
|
| 93 |
-
|
| 94 |
color = get_color(law)
|
| 95 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
f"<b>{node}</b><br>"
|
| 97 |
f"Зважений ступінь: {degree_dict.get(node, 0):,}<br>"
|
| 98 |
f"PageRank: {pr.get(node, 0):.6f}<br>"
|
| 99 |
-
f"Зв'язк
|
| 100 |
)
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
)
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
f'Макс. вага: <b>{filtered["weight"].max():,}</b>'
|
| 144 |
-
f'</div>'
|
| 145 |
)
|
| 146 |
|
| 147 |
-
return
|
| 148 |
|
| 149 |
|
| 150 |
-
def get_top_articles(n: int =
|
| 151 |
top = stats_df.nlargest(n, "total_citations")[
|
| 152 |
["law_number", "law_article", "citation_type", "total_citations", "unique_decisions"]
|
| 153 |
].copy()
|
|
@@ -176,20 +265,13 @@ def get_communities():
|
|
| 176 |
return df
|
| 177 |
|
| 178 |
|
| 179 |
-
# ---------------------------------------------------------------------------
|
| 180 |
-
# Gradio UI
|
| 181 |
-
# ---------------------------------------------------------------------------
|
| 182 |
-
|
| 183 |
with gr.Blocks(
|
| 184 |
title="Ukrainian Court Citation Graph",
|
| 185 |
theme=gr.themes.Base(
|
| 186 |
primary_hue="blue",
|
| 187 |
neutral_hue="slate",
|
| 188 |
),
|
| 189 |
-
css=""
|
| 190 |
-
.graph-container iframe { border: none; border-radius: 8px; }
|
| 191 |
-
footer { display: none !important; }
|
| 192 |
-
""",
|
| 193 |
) as demo:
|
| 194 |
gr.Markdown(
|
| 195 |
"""
|
|
@@ -220,16 +302,14 @@ with gr.Blocks(
|
|
| 220 |
info="Топ-N найважчих ребер",
|
| 221 |
)
|
| 222 |
layout = gr.Radio(
|
| 223 |
-
choices=["
|
| 224 |
-
value="
|
| 225 |
label="Алгоритм розташування",
|
| 226 |
)
|
| 227 |
btn = gr.Button("Побудувати граф", variant="primary", size="lg")
|
| 228 |
|
| 229 |
with gr.Column(scale=3):
|
| 230 |
-
graph_output = gr.
|
| 231 |
-
value="<div style='padding:40px;text-align:center;color:#666;'>Натисніть 'Побудувати граф' для візуалізації</div>",
|
| 232 |
-
)
|
| 233 |
|
| 234 |
btn.click(
|
| 235 |
fn=build_graph,
|
|
@@ -239,24 +319,15 @@ with gr.Blocks(
|
|
| 239 |
|
| 240 |
with gr.Tab("Топ статей за цитуваннями"):
|
| 241 |
gr.Markdown("### Найбільш цитовані нормативні положення")
|
| 242 |
-
|
| 243 |
-
value=get_top_articles(30),
|
| 244 |
-
interactive=False,
|
| 245 |
-
)
|
| 246 |
|
| 247 |
with gr.Tab("Centrality (PageRank, HITS)"):
|
| 248 |
gr.Markdown("### Топ-20 за PageRank у графі со-цитувань")
|
| 249 |
-
|
| 250 |
-
value=get_top_centrality(),
|
| 251 |
-
interactive=False,
|
| 252 |
-
)
|
| 253 |
|
| 254 |
with gr.Tab("Спільноти (Louvain)"):
|
| 255 |
gr.Markdown("### Виявлені спільноти за періодами (Louvain community detection)")
|
| 256 |
-
|
| 257 |
-
value=get_communities(),
|
| 258 |
-
interactive=False,
|
| 259 |
-
)
|
| 260 |
|
| 261 |
gr.Markdown(
|
| 262 |
"""
|
|
|
|
| 1 |
import gradio as gr
|
| 2 |
import pandas as pd
|
| 3 |
import networkx as nx
|
| 4 |
+
import plotly.graph_objects as go
|
| 5 |
from huggingface_hub import hf_hub_download
|
| 6 |
+
import numpy as np
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
|
| 8 |
REPO_ID = "overthelex/ua-court-citation-graph"
|
| 9 |
|
|
|
|
| 23 |
ALL_LAWS = sorted(edges_df["law_a"].unique().tolist())
|
| 24 |
|
| 25 |
DOMAIN_COLORS = {
|
| 26 |
+
"Кримінальний кодекс України": "#ef4444",
|
| 27 |
+
"Кримінальний процесуальний кодекс України": "#f97316",
|
| 28 |
+
"Цивільний кодекс України": "#3b82f6",
|
| 29 |
+
"Цивільний процесуальний кодекс України": "#60a5fa",
|
| 30 |
+
"Господарський кодекс України": "#22c55e",
|
| 31 |
+
"Господарський процесуальний кодекс України": "#4ade80",
|
| 32 |
+
"Кодекс адміністративного судочинства України": "#eab308",
|
| 33 |
+
"КУпАП": "#f59e0b",
|
| 34 |
+
"Податковий кодекс України": "#a855f7",
|
| 35 |
+
"Сімейний кодекс України": "#14b8a6",
|
| 36 |
+
"Земельний кодекс України": "#f97316",
|
| 37 |
+
"Конституція України": "#fbbf24",
|
| 38 |
}
|
| 39 |
+
DEFAULT_COLOR = "#94a3b8"
|
| 40 |
|
| 41 |
print(f"Loaded {len(edges_df):,} edges, {len(centrality_df)} centrality nodes, {len(stats_df):,} citation stats")
|
| 42 |
|
|
|
|
| 48 |
return DEFAULT_COLOR
|
| 49 |
|
| 50 |
|
| 51 |
+
SHORT_NAMES = [
|
| 52 |
+
("Кримінальний процесуальний кодекс України", "КПК"),
|
| 53 |
+
("Кримінальний кодекс України", "ККУ"),
|
| 54 |
+
("Цивільний процесуальний кодекс України", "ЦПК"),
|
| 55 |
+
("Цивільний кодекс України", "ЦКУ"),
|
| 56 |
+
("Господарський процесуальний кодекс України", "ГПК"),
|
| 57 |
+
("Господарський кодекс України", "ГКУ"),
|
| 58 |
+
("Кодекс адміністративного судочинства України", "КАСУ"),
|
| 59 |
+
("Податковий кодекс України", "ПКУ"),
|
| 60 |
+
("Сімейний кодекс України", "СКУ"),
|
| 61 |
+
("Земельний кодекс України", "ЗКУ"),
|
| 62 |
+
("Конституція України", "КУ"),
|
| 63 |
+
]
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def shorten(name: str) -> str:
|
| 67 |
+
for long, short in SHORT_NAMES:
|
| 68 |
+
if name == long:
|
| 69 |
+
return short
|
| 70 |
+
if len(name) > 20:
|
| 71 |
+
return name[:18] + "..."
|
| 72 |
+
return name
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def build_graph(law_filter: list[str], min_weight: int, max_edges: int, layout: str):
|
| 76 |
filtered = edges_df[edges_df["weight"] >= min_weight]
|
| 77 |
|
| 78 |
if law_filter:
|
|
|
|
| 82 |
filtered = filtered.nlargest(max_edges, "weight")
|
| 83 |
|
| 84 |
if filtered.empty:
|
| 85 |
+
fig = go.Figure()
|
| 86 |
+
fig.update_layout(
|
| 87 |
+
paper_bgcolor="#0f172a", plot_bgcolor="#0f172a",
|
| 88 |
+
annotations=[dict(text="Немає ребер. Зменшіть мінімальну вагу.",
|
| 89 |
+
showarrow=False, font=dict(size=18, color="#94a3b8"),
|
| 90 |
+
xref="paper", yref="paper", x=0.5, y=0.5)],
|
| 91 |
+
xaxis=dict(visible=False), yaxis=dict(visible=False),
|
| 92 |
+
height=750,
|
| 93 |
+
)
|
| 94 |
+
return fig
|
| 95 |
|
| 96 |
G = nx.Graph()
|
| 97 |
for _, row in filtered.iterrows():
|
|
|
|
| 100 |
degree_dict = dict(G.degree(weight="weight"))
|
| 101 |
pr = nx.pagerank(G, weight="weight")
|
| 102 |
|
| 103 |
+
if layout == "Kamada-Kawai":
|
| 104 |
+
pos = nx.kamada_kawai_layout(G, weight="weight")
|
| 105 |
+
else:
|
| 106 |
+
pos = nx.spring_layout(G, k=2.5 / np.sqrt(G.number_of_nodes()),
|
| 107 |
+
iterations=100, weight="weight", seed=42)
|
| 108 |
+
|
| 109 |
+
max_w = filtered["weight"].max()
|
| 110 |
+
min_w = filtered["weight"].min()
|
| 111 |
+
w_range = max_w - min_w if max_w > min_w else 1
|
| 112 |
+
|
| 113 |
+
edge_x, edge_y, edge_hover = [], [], []
|
| 114 |
+
for u, v, d in G.edges(data=True):
|
| 115 |
+
x0, y0 = pos[u]
|
| 116 |
+
x1, y1 = pos[v]
|
| 117 |
+
edge_x.extend([x0, x1, None])
|
| 118 |
+
edge_y.extend([y0, y1, None])
|
| 119 |
+
|
| 120 |
+
edge_trace = go.Scatter(
|
| 121 |
+
x=edge_x, y=edge_y,
|
| 122 |
+
mode="lines",
|
| 123 |
+
line=dict(width=0.5, color="rgba(148,163,184,0.2)"),
|
| 124 |
+
hoverinfo="none",
|
| 125 |
+
showlegend=False,
|
| 126 |
)
|
| 127 |
|
| 128 |
+
thick_edge_traces = []
|
| 129 |
+
sorted_edges = sorted(G.edges(data=True), key=lambda e: e[2]["weight"], reverse=True)
|
| 130 |
+
top_n = min(50, len(sorted_edges))
|
| 131 |
+
for u, v, d in sorted_edges[:top_n]:
|
| 132 |
+
x0, y0 = pos[u]
|
| 133 |
+
x1, y1 = pos[v]
|
| 134 |
+
w = d["weight"]
|
| 135 |
+
width = 1 + 5 * ((w - min_w) / w_range)
|
| 136 |
+
opacity = 0.3 + 0.5 * ((w - min_w) / w_range)
|
| 137 |
+
thick_edge_traces.append(go.Scatter(
|
| 138 |
+
x=[x0, x1, None], y=[y0, y1, None],
|
| 139 |
+
mode="lines",
|
| 140 |
+
line=dict(width=width, color=f"rgba(96,165,250,{opacity:.2f})"),
|
| 141 |
+
hoverinfo="text",
|
| 142 |
+
hovertext=f"{u} ↔ {v}<br>Вага: {w:,} рішень",
|
| 143 |
+
showlegend=False,
|
| 144 |
+
))
|
| 145 |
+
|
| 146 |
max_pr = max(pr.values()) if pr else 1
|
| 147 |
+
node_groups: dict[str, dict] = {}
|
| 148 |
for node in G.nodes():
|
| 149 |
parts = node.split(" ст. ", 1)
|
| 150 |
+
law = parts[0] if len(parts) == 2 else "Інше"
|
| 151 |
+
article = parts[1] if len(parts) == 2 else node
|
| 152 |
color = get_color(law)
|
| 153 |
+
|
| 154 |
+
if law not in node_groups:
|
| 155 |
+
node_groups[law] = {"x": [], "y": [], "text": [], "hover": [],
|
| 156 |
+
"size": [], "color": color}
|
| 157 |
+
|
| 158 |
+
x, y = pos[node]
|
| 159 |
+
size = 8 + 40 * (pr.get(node, 0) / max_pr)
|
| 160 |
+
hover = (
|
| 161 |
f"<b>{node}</b><br>"
|
| 162 |
f"Зважений ступінь: {degree_dict.get(node, 0):,}<br>"
|
| 163 |
f"PageRank: {pr.get(node, 0):.6f}<br>"
|
| 164 |
+
f"Зв'язків: {G.degree(node)}"
|
| 165 |
)
|
| 166 |
+
node_groups[law]["x"].append(x)
|
| 167 |
+
node_groups[law]["y"].append(y)
|
| 168 |
+
node_groups[law]["text"].append(f"ст. {article}")
|
| 169 |
+
node_groups[law]["hover"].append(hover)
|
| 170 |
+
node_groups[law]["size"].append(size)
|
| 171 |
+
|
| 172 |
+
node_traces = []
|
| 173 |
+
for group_name in sorted(node_groups.keys()):
|
| 174 |
+
data = node_groups[group_name]
|
| 175 |
+
node_traces.append(go.Scatter(
|
| 176 |
+
x=data["x"], y=data["y"],
|
| 177 |
+
mode="markers+text",
|
| 178 |
+
marker=dict(
|
| 179 |
+
size=data["size"],
|
| 180 |
+
color=data["color"],
|
| 181 |
+
line=dict(width=1.5, color="rgba(255,255,255,0.4)"),
|
| 182 |
+
),
|
| 183 |
+
text=data["text"],
|
| 184 |
+
textposition="top center",
|
| 185 |
+
textfont=dict(size=9, color="#cbd5e1"),
|
| 186 |
+
hoverinfo="text",
|
| 187 |
+
hovertext=data["hover"],
|
| 188 |
+
name=shorten(group_name),
|
| 189 |
+
))
|
| 190 |
+
|
| 191 |
+
fig = go.Figure(data=[edge_trace] + thick_edge_traces + node_traces)
|
| 192 |
+
|
| 193 |
+
fig.update_layout(
|
| 194 |
+
paper_bgcolor="#0f172a",
|
| 195 |
+
plot_bgcolor="#0f172a",
|
| 196 |
+
height=750,
|
| 197 |
+
margin=dict(l=5, r=5, t=45, b=5),
|
| 198 |
+
title=dict(
|
| 199 |
+
text=(f"Вузлів: {G.number_of_nodes()} | "
|
| 200 |
+
f"Ребер: {G.number_of_edges()} | "
|
| 201 |
+
f"Мін. вага: {filtered['weight'].min():,} | "
|
| 202 |
+
f"Макс. вага: {filtered['weight'].max():,}"),
|
| 203 |
+
font=dict(size=13, color="#94a3b8"),
|
| 204 |
+
x=0.5,
|
| 205 |
+
),
|
| 206 |
+
xaxis=dict(visible=False, showgrid=False, zeroline=False),
|
| 207 |
+
yaxis=dict(visible=False, showgrid=False, zeroline=False),
|
| 208 |
+
legend=dict(
|
| 209 |
+
font=dict(color="#e2e8f0", size=11),
|
| 210 |
+
bgcolor="rgba(15,23,42,0.9)",
|
| 211 |
+
bordercolor="rgba(71,85,105,0.5)",
|
| 212 |
+
borderwidth=1,
|
| 213 |
+
orientation="h",
|
| 214 |
+
yanchor="bottom",
|
| 215 |
+
y=1.02,
|
| 216 |
+
xanchor="center",
|
| 217 |
+
x=0.5,
|
| 218 |
+
),
|
| 219 |
+
hoverlabel=dict(
|
| 220 |
+
bgcolor="#1e293b",
|
| 221 |
+
font_size=13,
|
| 222 |
+
font_color="#e2e8f0",
|
| 223 |
+
bordercolor="#475569",
|
| 224 |
+
),
|
| 225 |
+
dragmode="pan",
|
| 226 |
)
|
| 227 |
+
|
| 228 |
+
fig.update_layout(
|
| 229 |
+
modebar=dict(
|
| 230 |
+
bgcolor="rgba(15,23,42,0.8)",
|
| 231 |
+
color="#94a3b8",
|
| 232 |
+
activecolor="#60a5fa",
|
| 233 |
+
)
|
|
|
|
|
|
|
| 234 |
)
|
| 235 |
|
| 236 |
+
return fig
|
| 237 |
|
| 238 |
|
| 239 |
+
def get_top_articles(n: int = 30):
|
| 240 |
top = stats_df.nlargest(n, "total_citations")[
|
| 241 |
["law_number", "law_article", "citation_type", "total_citations", "unique_decisions"]
|
| 242 |
].copy()
|
|
|
|
| 265 |
return df
|
| 266 |
|
| 267 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 268 |
with gr.Blocks(
|
| 269 |
title="Ukrainian Court Citation Graph",
|
| 270 |
theme=gr.themes.Base(
|
| 271 |
primary_hue="blue",
|
| 272 |
neutral_hue="slate",
|
| 273 |
),
|
| 274 |
+
css="footer { display: none !important; }",
|
|
|
|
|
|
|
|
|
|
| 275 |
) as demo:
|
| 276 |
gr.Markdown(
|
| 277 |
"""
|
|
|
|
| 302 |
info="Топ-N найважчих ребер",
|
| 303 |
)
|
| 304 |
layout = gr.Radio(
|
| 305 |
+
choices=["Spring", "Kamada-Kawai"],
|
| 306 |
+
value="Spring",
|
| 307 |
label="Алгоритм розташування",
|
| 308 |
)
|
| 309 |
btn = gr.Button("Побудувати граф", variant="primary", size="lg")
|
| 310 |
|
| 311 |
with gr.Column(scale=3):
|
| 312 |
+
graph_output = gr.Plot()
|
|
|
|
|
|
|
| 313 |
|
| 314 |
btn.click(
|
| 315 |
fn=build_graph,
|
|
|
|
| 319 |
|
| 320 |
with gr.Tab("Топ статей за цитуваннями"):
|
| 321 |
gr.Markdown("### Найбільш цитовані нормативні положення")
|
| 322 |
+
gr.Dataframe(value=get_top_articles(30), interactive=False)
|
|
|
|
|
|
|
|
|
|
| 323 |
|
| 324 |
with gr.Tab("Centrality (PageRank, HITS)"):
|
| 325 |
gr.Markdown("### Топ-20 за PageRank у графі со-цитувань")
|
| 326 |
+
gr.Dataframe(value=get_top_centrality(), interactive=False)
|
|
|
|
|
|
|
|
|
|
| 327 |
|
| 328 |
with gr.Tab("Спільноти (Louvain)"):
|
| 329 |
gr.Markdown("### Виявлені спільноти за періодами (Louvain community detection)")
|
| 330 |
+
gr.Dataframe(value=get_communities(), interactive=False)
|
|
|
|
|
|
|
|
|
|
| 331 |
|
| 332 |
gr.Markdown(
|
| 333 |
"""
|
requirements.txt
CHANGED
|
@@ -3,5 +3,6 @@ huggingface_hub
|
|
| 3 |
pandas
|
| 4 |
pyarrow
|
| 5 |
networkx
|
| 6 |
-
|
| 7 |
scipy
|
|
|
|
|
|
| 3 |
pandas
|
| 4 |
pyarrow
|
| 5 |
networkx
|
| 6 |
+
plotly
|
| 7 |
scipy
|
| 8 |
+
numpy
|