adriens Claude Sonnet 4.6 commited on
Commit
a2178d7
·
1 Parent(s): 59fb2ba

feat: add optimal threshold chart (Jenks + KMeans convergence at 99 chars)

Browse files

Histogramme bicolore avec les 2 clusters, centroïdes et seuil à 99 chars.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Files changed (1) hide show
  1. app.py +38 -0
app.py CHANGED
@@ -233,3 +233,41 @@ cdf_line = alt.Chart(cdf_data).mark_line(color="#1f77b4", strokeWidth=2).encode(
233
  )
234
  st.altair_chart(cdf_line + rule, use_container_width=True)
235
  st.caption(f"La ligne rouge marque le seuil à **{threshold} caractères** — {pct_free:.1f}% des messages sont gratuits, {pct_paid:.1f}% sont payants.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
233
  )
234
  st.altair_chart(cdf_line + rule, use_container_width=True)
235
  st.caption(f"La ligne rouge marque le seuil à **{threshold} caractères** — {pct_free:.1f}% des messages sont gratuits, {pct_paid:.1f}% sont payants.")
236
+
237
+ st.divider()
238
+
239
+ # --- Détermination du seuil optimal (Jenks + KMeans) ---
240
+ st.subheader("📐 Détermination du seuil optimal — Jenks & KMeans")
241
+ st.caption("Les deux méthodes convergent à **99 caractères** : frontière naturelle entre deux populations distinctes.")
242
+
243
+ SPLIT = 99
244
+ CENTER_SHORT, CENTER_LONG = 61.5, 137.0
245
+
246
+ col_a, col_b = st.columns(2)
247
+ col_a.metric("Centroïde messages courts", f"{CENTER_SHORT:.0f} chars", "cluster 1")
248
+ col_b.metric("Centroïde messages longs", f"{CENTER_LONG:.0f} chars", "cluster 2")
249
+
250
+ # Histogramme avec les 2 clusters colorés
251
+ hist_data = pd.DataFrame({"length": lengths})
252
+ hist_data["tier"] = hist_data["length"].apply(lambda x: "≤ 99 chars (gratuit)" if x <= SPLIT else "> 99 chars (auth requise)")
253
+
254
+ hist_chart = alt.Chart(hist_data).mark_bar(opacity=0.8).encode(
255
+ x=alt.X("length:Q", bin=alt.Bin(maxbins=60), title="Longueur (caractères)"),
256
+ y=alt.Y("count():Q", title="Fréquence"),
257
+ color=alt.Color("tier:N", scale=alt.Scale(
258
+ domain=["≤ 99 chars (gratuit)", "> 99 chars (auth requise)"],
259
+ range=["#1f77b4", "#d62728"]
260
+ ), title="Tier"),
261
+ tooltip=["tier", "count()"],
262
+ ).properties(height=300)
263
+
264
+ centroid_data = pd.DataFrame({"x": [CENTER_SHORT, CENTER_LONG], "label": ["Centre cluster 1", "Centre cluster 2"]})
265
+ centroid_rules = alt.Chart(centroid_data).mark_rule(strokeDash=[4, 2], strokeWidth=1.5).encode(
266
+ x="x:Q", color=alt.Color("label:N", scale=alt.Scale(range=["#1f77b4", "#d62728"]), legend=None)
267
+ )
268
+ split_rule = alt.Chart(pd.DataFrame({"x": [SPLIT]})).mark_rule(
269
+ color="black", strokeWidth=2
270
+ ).encode(x="x:Q")
271
+
272
+ st.altair_chart(hist_chart + centroid_rules + split_rule, use_container_width=True)
273
+ st.caption("**Ligne noire** = seuil à 99 chars · **lignes pointillées** = centroïdes des deux clusters · bleu = gratuit · rouge = authentification requise")