انتقل إلى المحتوى الرئيسي

الوحدة 4 — الرسوم البيانية والجداول

بعد أن هيكلنا الصفحة، نملؤها. تعرض هذه الوحدة ثلاث طبقات من الرسم في Streamlit، ومكوّنَي جدول أحدهما تفاعليّ والآخر قابل للتحرير، ثمّ نبني رسوم متابعة الخطر التي تظهر في تبويب «نظرة عامّة» من الوحدة السابقة.

ثلاث طبقات للرسم

Streamlit لا يعرض رسمًا واحدًا، بل ثلاث طبقات تُغطّي احتياجات مختلفة.

الطبقة السريعة: st.line_chart، st.bar_chart، st.area_chart. تأخذ DataFrame وترسم مباشرة. سطر واحد، بلا تكوين. مفيدة للاستكشاف الأوّل وللوحات مقتضبة.

الطبقة المتوسّطة: st.plotly_chart(fig) وst.altair_chart(chart). تفاعليّة (تكبير، تحويم، تصفية). تتطلّب بضعة أسطر إضافيّة لكنّها تعطي تحكّمًا كاملًا في المحاور والألوان والتلميحات.

الطبقة الأدنى: st.pyplot(fig) لمن يأتي من Matplotlib. جامدة (بلا تفاعل)، لكنّها تسمح باستعمال شيفرة موجودة سابقًا.

القاعدة العمليّة: ابدأ بالطبقة الأدنى صعوبةً وارتفع فقط عند الحاجة. لصفحة داخليّة، st.line_chart كافٍ. للوحة عرض عامّة، Plotly يستحقّ الأسطر الإضافيّة لتفاعله وتلميحاته.

الرسوم الأصليّة

الأبسط أوّلًا. st.line_chart(df) يرسم كلّ عمود عدديّ كخطّ منفصل، مع فهرس df كمحور أفقيّ:

import streamlit as st
import pandas as pd
import numpy as np

dates = pd.date_range("2025-01-01", periods=60, freq="D")
suivi = pd.DataFrame({
"date": dates,
"moyen": np.linspace(0.34, 0.42, 60) + np.random.normal(0, 0.02, 60),
"median": np.linspace(0.30, 0.37, 60) + np.random.normal(0, 0.02, 60),
}).set_index("date")

st.subheader("تطوّر متوسّط درجة الخطر")
st.line_chart(suivi)

st.bar_chart(df) بالمنطق نفسه. st.area_chart(df) كذلك، مع تظليل تحت الخطوط.

المعلمات المهمّة: x وy لاختيار عمودَين صريحَين، color لعمود يُلوّن الخطوط، use_container_width=True (افتراضيّ) لعرض بعرض الحاوية.

Plotly

Plotly المكتبة الأشيَع للرسوم التفاعليّة. الاستعمال في Streamlit في سطرَين:

import plotly.express as px
import streamlit as st

fig = px.histogram(
donnees,
x="score_risque",
color="offre",
nbins=30,
title="توزيع درجة الخطر حسب العرض",
labels={"score_risque": "درجة الخطر", "offre": "نوع العرض"},
)
fig.update_layout(bargap=0.05)
st.plotly_chart(fig, use_container_width=True)

المزايا العمليّة: تكبير بعجلة الفأرة، تحويم يُعرض القيم، نقر على العنصر في اللغة يُخفيها. لا حاجة إلى معالجات أحداث في شيفرة Streamlit، Plotly يديرها داخليًّا.

لتقاطع Plotly مع Streamlit التفاعليّ (اختيار نقطة يُعيد شيئًا لـPython)، st.plotly_chart يقبل معلمة on_select="rerun" منذ الإصدار 1.30، ويُعيد قاموس التحديدات. لكن هذه الحالة نادرة؛ الاستعمال الأشيع هو عرض بسيط.

Altair

Altair سهل حين تُريد رسمًا معقّدًا في بضعة أسطر. مثال متوسّط:

import altair as alt
import streamlit as st

chart = (
alt.Chart(donnees)
.mark_circle(size=60)
.encode(
x=alt.X("anciennete_mois:Q", title="الأقدميّة (شهر)"),
y=alt.Y("score_risque:Q", title="درجة الخطر"),
color=alt.Color("offre:N", legend=alt.Legend(title="العرض")),
tooltip=["identifiant", "anciennete_mois", "score_risque", "offre"],
)
.interactive()
.properties(height=380)
)

st.altair_chart(chart, use_container_width=True)

Altair يعتمد قواعد بصريّة صريحة (Grammar of Graphics). أنيق للأشخاص الذين يعرفون ggplot، أقلّ ألفة لغيرهم. لهذه الدورة نستعمل Plotly كخيار افتراضيّ، ونذكّر بـAltair كبديل.

الجداول التفاعليّة (st.dataframe)

st.dataframe(df) يعرض جدولًا مع فرز على كلّ عمود، تصفّح، تحديد صفوف، وبحث. المعلمات الأكثر فائدة:

  • column_config لضبط تنسيق كلّ عمود (نوع، عرض، صورة، رابط…).
  • hide_index=True لإخفاء الفهرس الافتراضيّ.
  • use_container_width=True لملء العرض المتاح.
  • height لتحديد ارتفاع ثابت.

مثال محسَّن لجدول العملاء:

import streamlit as st

st.dataframe(
clients_a_risque,
hide_index=True,
use_container_width=True,
height=420,
column_config={
"identifiant": st.column_config.TextColumn("المعرّف", width="small"),
"score_risque": st.column_config.ProgressColumn(
"الخطر", min_value=0, max_value=1, format="%.2f",
),
"mensuel_usd": st.column_config.NumberColumn(
"المبلغ الشهريّ", format="%.2f USD",
),
"date_derniere_interaction": st.column_config.DateColumn(
"آخر تفاعل", format="YYYY-MM-DD",
),
},
)

ProgressColumn تعرض شريط تقدّم داخل الخليّة، أنيق لدرجة بين 0 و1. NumberColumn تُنسِّق العملة. DateColumn تُوحِّد صيغة التاريخ.

الجداول القابلة للتحرير (st.data_editor)

نفس الواجهة، لكنّه يُعيد إطار البيانات المُعدَّل. مثاليّ لواجهة مصحّح يُعدّل قيمًا يدويًّا:

import streamlit as st

corrections = st.data_editor(
clients_a_verifier,
num_rows="dynamic", # يسمح بإضافة/حذف صفوف
hide_index=True,
column_config={
"commentaire": st.column_config.TextColumn("ملاحظة", required=False),
"faux_positif": st.column_config.CheckboxColumn("خطأ إيجابيّ؟", default=False),
},
)

if st.button("حفظ التصحيحات"):
corrections.to_csv("corrections.csv", index=False)
st.success(f"حُفظ {len(corrections)} صفًّا.")

القيمة المُعادة هي إطار بيانات جديد؛ التعديلات تختفي إذا لم تُخزَّن (في ملفّ، قاعدة بيانات، أو session_state). هذا مقصود: Streamlit لا يقرّر بنفسه أين تُحفظ.

رسوم الخيط الأحمر

نجمّع الآن رسوم لوحة الخطر:

import streamlit as st
import pandas as pd
import plotly.express as px
import numpy as np

rng = np.random.default_rng(7)
donnees = pd.DataFrame({
"identifiant": [f"C{i:04d}" for i in range(500)],
"anciennete_mois": rng.integers(1, 72, 500),
"score_risque": rng.beta(2, 5, 500).round(3),
"offre": rng.choice(["Basic", "Standard", "Premium", "Family"], 500, p=[0.2, 0.4, 0.3, 0.1]),
"mensuel_usd": (rng.gamma(2.5, 15, 500) + 10).round(2),
})

tab_dist, tab_disp, tab_tab = st.tabs(["توزيع الخطر", "الأقدميّة مقابل الخطر", "جدول"])

with tab_dist:
fig = px.histogram(donnees, x="score_risque", color="offre", nbins=30,
labels={"score_risque": "درجة الخطر", "offre": "العرض"})
st.plotly_chart(fig, use_container_width=True)

with tab_disp:
fig = px.scatter(donnees, x="anciennete_mois", y="score_risque", color="offre",
hover_data=["identifiant", "mensuel_usd"],
labels={"anciennete_mois": "الأقدميّة (شهر)", "score_risque": "درجة الخطر"})
st.plotly_chart(fig, use_container_width=True)

with tab_tab:
st.dataframe(
donnees.sort_values("score_risque", ascending=False).head(50),
hide_index=True,
use_container_width=True,
column_config={
"score_risque": st.column_config.ProgressColumn("الخطر", min_value=0, max_value=1, format="%.2f"),
"mensuel_usd": st.column_config.NumberColumn("USD/شهر", format="%.2f"),
},
)

كلّ تبويب مستقلّ بصريًّا لكنّه يستعمل الإطار نفسه. حين نُضيف مرشّحات الشريط الجانبيّ في الوحدة 6، ستُطبَّق على الإطار قبل تمريره للرسوم.

الاتّجاه العربيّ للنصوص في الرسوم

Plotly وAltair يعرضان النصّ العربيّ بشكل صحيح (كلاهما يستعمل عرض متّجه SVG)، لكنّ ترتيب المحور الأفقيّ يبقى من اليسار إلى اليمين. هذا مقبول في السياق العلميّ: القراء المتّجهون للأرقام يتوقّعون هذا الترتيب. لعكس ترتيب الفئات على محور فئويّ (مثلًا Basic → Family)، استعمل fig.update_xaxes(autorange="reversed") عند الضرورة، ولكن اختبر النتيجة أوّلًا.

الفخّ: رسم مليون نقطة

plotly_chart يعرض النقاط الأصليّة كلّها. رسم مبعثر لـمليون عميل يُجمّد المتصفّح ولا يُضيف معلومة (النقاط تُغطّي بعضها). حلول عمليّة: عيّنة عشوائيّة معايرَة (df.sample(5000))، أو تجميع في خلايا (px.density_heatmap)، أو استعمال WebGL بـpx.scatter(..., render_mode="webgl"). القاعدة: أكثر من عشرة آلاف نقطة يستوجب حلًّا واحدًا من الثلاثة.

الخلاصة

  • ثلاث طبقات رسم: الأصليّة (st.line_chart)، المتوسّطة (Plotly, Altair)، الأدنى (Matplotlib). ابدأ بالأبسط.
  • st.dataframe للعرض التفاعليّ، st.data_editor للتحرير مع حفظ صريح للنتيجة.
  • column_config يحوّل جدولًا خامًا إلى واجهة قابلة للقراءة (ProgressColumn، NumberColumn، DateColumn).
  • أكثر من عشرة آلاف نقطة على رسم مبعثر يستوجب عيّنة أو خريطة كثافة أو WebGL.

الوحدة التالية: التخزين المؤقّت الذي يجعل هذه الرسوم لا تُعاد قراءتها من الملفّ عند كلّ نقرة.