- KFS × Navera
- IoT & AI for Smart Agriculture
- السيشن 13
مقدمة في تحليل البيانات الزراعية بـ Python
Introduction to Agricultural Data Analysis (Python)
من الأرقام إلى المعرفة: Jupyter و Pandas و NumPy على بيانات طقس وتربة حقيقية لموقع مزرعة الكلية (2023–2024): قراءة السلاسل الزمنية، الإحصاء الوصفي، التجميع حسب الوقت والموقع، الدورة اليومية، وكشف فترات الجفاف — وتقرير إحصائي عن حالة المزرعة.
نظرة عامة على السيشن
بنهاية السيشن ستكون قادراً على
- تستخدم Jupyter Notebook وتقرأ CSV بـ Pandas مع تحويل عمود الوقت إلى DatetimeIndex.
- تحسب الإحصاء الوصفي (mean, std, min, max, quantiles) وتفسره زراعياً.
- تجمّع السلاسل الزمنية بـ resample (يومي/أسبوعي/شهري) و groupby (ساعة اليوم، العقدة).
- تستخدم NumPy وشروط منطقية لكشف فترات الجفاف المتصلة.
- ترسم بـ Matplotlib وتكتب استنتاجات زراعية مدعومة بالأرقام.
التطبيق في مزرعة الكلية
تحليل رطوبة التربة لأسبوع من عقد المزرعة نفسها (farm.db) ولسنتين من بيانات Open-Meteo لموقع الكلية: متى تجف التربة؟ ما أحر ساعة؟ كم الفرق بين البخر-نتح والمطر شهرياً؟ وتحديد فترات الجفاف.
المُخرج (Deliverable)
تقرير إحصائي بسيط عن ظروف المزرعة (Notebook + PDF): جدول شهري، الدورة اليومية، فترات الجفاف، ورسم شامل — مبني على بيانات حقيقية.
خطة السيشن (180 دقيقة)
- 0:00 مراجعة مراجعة + لماذا البيانات أهم من الحساس؟ 10 د
- 0:10 شرح Python للبيانات: Jupyter، Series و DataFrame، NumPy 30 د
- 0:40 تطبيق Lab: استكشاف بيانات المزرعة خطوة بخطوة 35 د
- 1:15 استراحة استراحة 10 د
- 1:25 مشروع المشروع الجاهز: كشف فترات الجفاف والتقرير الأسبوعي 30 د
- 1:55 ميداني ميداني/تحليلي: أسبوع من عقد المزرعة نفسها 25 د
- 2:20 مشروع كتابة التقرير الإحصائي + عرض النتائج 30 د
- 2:50 تقييم اختبار سريع + الواجب 10 د
الأدوات والمكونات
قبل السيشن
pip install pandas numpy matplotlib jupyter- حمّل agritech-datasets.zip و agritech-ai-projects.zip وفكّهما في مجلد واحد (datasets و projects بجوار بعض).
قاموس المصطلحات (Glossary)
جدول بأعمدة مسماة — مثل ورقة Excel داخل Python.
عمود واحد بفهرس.
فهرس من تواريخ يسمح بـ resample والقص بالتاريخ.
تجميع لفترات: D يومي، W أسبوعي، MS شهري.
حساب إحصاء لكل مجموعة (ساعة، عقدة، شهر).
المتوسط، الانحراف المعياري، الأدنى، الأعلى، الربيعيات.
القيمة التي يقل عنها X% من البيانات.
كمية الماء التي يفقدها محصول مرجعي (نجيل) — FAO-56.
المطر − البخر-نتح: سالب = عجز يجب تعويضه بالري.
بيانات مناخية محسوبة من نماذج + أرصاد (ERA5) لأي موقع.
سلسلة True/False لاختيار صفوف تحقق شرطاً.
تغير المتغير على مدار ساعات اليوم.
Python للبيانات
30 دقيقةKFS farm hourly weather & soil 2023–2024
بيانات حقيقية ساعية لموقع مزرعة كلية الزراعة بكفر الشيخ (31.10°N, 30.94°E): حرارة ورطوبة الهواء، المطر، حرارة التربة، رطوبة التربة على عمقين، البخر-نتح المرجعي، الإشعاع الشمسي والرياح. يمكن إعادة تحميلها لأي فترة من رابط الـ API نفسه.
- الحجم17,544 صف (ساعة) × 10 أعمدة
- الترخيصCC BY 4.0 (Open-Meteo)
- نوع البياناتحقيقية — إعادة تحليل لموقع المزرعة
الأعمدة (Columns)
time | الوقت (توقيت القاهرة) |
air_temp_c | حرارة الهواء على 2 م °C |
air_humidity_pct | الرطوبة النسبية % |
precipitation_mm | المطر مم/ساعة |
soil_temp_c | حرارة التربة 0–7 سم |
soil_moisture_0_7cm | رطوبة التربة السطحية m³/m³ |
soil_moisture_7_28cm | رطوبة التربة 7–28 سم m³/m³ |
et0_mm | البخر-نتح المرجعي FAO مم/ساعة |
solar_radiation_wm2 | الإشعاع الشمسي W/m² |
wind_speed_kmh | سرعة الرياح على 10 م |
افهم بياناتك قبل تحليلها
رطوبة التربة في Open-Meteo محسوبة لخلية شبكية غير مروية (متوسط ~0.12 m³/m³)، أي تمثل أرضاً بعلية في مناخ جاف — وليست حقلاً مروياً. لذلك نستخدمها لفهم المناخ والبخر-نتح، ونستخدم بيانات عقدنا (والمحاكاة FAO-56 في السيشن 15) لقرارات الري.
import pandas as pd
import numpy as np
df = pd.read_csv("datasets/kfs_farm_hourly_2023_2024.csv", parse_dates=["time"])
df = df.set_index("time").sort_index() # DatetimeIndex -> time superpowers
df.head() # first 5 rows
df.info() # types + missing values
df.describe().T # descriptive statistics for every column
df.loc["2024-07-15"] # all hours of one day (slicing by date string!)
Lab: استكشاف بيانات المزرعة
35 دقيقةstats = df[["air_temp_c", "air_humidity_pct", "soil_moisture_0_7cm", "et0_mm"]].describe().round(2)
print(stats)
print("hours above 40 °C:", (df["air_temp_c"] > 40).sum())
print("10th / 90th percentile of topsoil VWC:", df["soil_moisture_0_7cm"].quantile([.1, .9]).round(3).tolist())
monthly = df.resample("MS").agg({"air_temp_c": "mean", "precipitation_mm": "sum", "et0_mm": "sum"}).round(1)
monthly["water_balance_mm"] = monthly["precipitation_mm"] - monthly["et0_mm"] # negative = irrigation deficit
print(monthly.loc["2023"])
print("Annual ET0 2023 (mm):", round(df.loc["2023", "et0_mm"].sum()))
print("Annual rain 2023 (mm):", round(df.loc["2023", "precipitation_mm"].sum(), 1))
summer = df.loc["2024-06":"2024-08"]
diurnal = summer.groupby(summer.index.hour)[["air_temp_c", "air_humidity_pct", "et0_mm"]].mean().round(2)
print(diurnal)
print("hottest hour:", diurnal["air_temp_c"].idxmax(), "| peak ET0 hour:", diurnal["et0_mm"].idxmax())
# agronomic reading: irrigate early morning (lowest ET0 + lowest temperature) to cut evaporation losses
ماذا تقول بيانات مزرعة الكلية؟ (2023)
المخرجات الفعلية من سكربت السيشن (الجدول الشهري لعام 2023) — لاحظ أن الميزان المائي سالب كل شهر: الري ليس اختياراً في الدلتا.
المخرجات الفعلية عند التشغيل (Real output)
air_temp_c air_humidity_pct precipitation_mm et0_mm soil_moisture_0_7cm water_balance_mm 2023-01-01 14.83 71.33 6.8 76.51 0.17 -69.7 2023-04-01 20.35 54.11 3.7 162.70 0.14 -159.0 2023-07-01 29.81 57.83 0.0 235.74 0.12 -235.7 2023-10-01 24.83 64.95 8.2 131.86 0.13 -123.7 2023-12-01 17.69 69.91 4.9 78.36 0.13 -73.5 == Hour of hottest air (mean): 13 :00
فكّر وناقش
يوليو: 236 مم بخر-نتح مقابل 0 مم مطر. كم متر مكعب ماء يحتاج فدان (4200 م²) لتعويض ذلك؟ (1 مم = 1 لتر/م²)
لماذا يجب ري الصباح الباكر وليس الظهر؟ استخدم جدول الدورة اليومية.
المشروع الجاهز: فترات الجفاف
30 دقيقة«فترة جفاف» = ساعات متصلة تكون فيها رطوبة التربة أقل من عتبة. الحيلة البرمجية: نحول الشرط إلى True/False، ثم نعطي رقماً لكل «كتلة» متصلة بـ (dry != dry.shift()).cumsum()، ثم نجمع كل كتلة.
dry = df["soil_moisture_0_7cm"] < 0.110 # boolean mask (threshold = 20th percentile of this cell)
block = (dry != dry.shift()).cumsum() # new id every time the state flips
periods = df[dry].groupby(block[dry]).agg(
start=("soil_moisture_0_7cm", lambda s: s.index.min()),
hours=("soil_moisture_0_7cm", "size"))
print(periods[periods.hours >= 48].sort_values("hours", ascending=False).head())
s13_explore_farm_data.py — التحليل الكامل
السكربت يحسب الإحصاء والملف الشهري والدورة اليومية والتقرير الأسبوعي لصيف 2024 وفترات الجفاف ≥ 48 ساعة، ويرسم شكلاً من 3 لوحات (الحرارة، المطر، رطوبة التربة مع خط العتبة).
cd projects/ai
python s13_explore_farm_data.py
المخرجات الفعلية عند التشغيل (Real output)
Rows: 17544 | from 2023-01-01 00:00:00 to 2024-12-31 23:00:00
...
== Drought periods (>=48 h below 0.11 m3/m3): 8
start end hours min_vwc
2024-11-01 15:00:00 2024-12-21 14:00:00 1200 0.052
2024-09-12 15:00:00 2024-11-01 08:00:00 1194 0.089
2024-12-22 03:00:00 2024-12-31 23:00:00 237 0.057
2024-06-19 15:00:00 2024-06-25 02:00:00 132 0.108
2024-06-25 15:00:00 2024-07-01 02:00:00 132 0.105
Saved outputs to ...\projects\ai\outputs
Correlation soil moisture vs ET0 (daily): -0.309"""Session 13 - Agricultural data analysis with Pandas & NumPy.
Goal: describe the climate/soil of the KFS farm and detect drought periods.
Run: python s13_explore_farm_data.py
Outputs (outputs/): s13_weekly_report.csv, s13_drought_periods.csv, s13_overview.png
"""
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
pd.set_option("display.width", 160)
pd.set_option("display.max_columns", 20)
from common import OUT, RAINFED_DRY as REFILL_POINT, load_hourly
df = load_hourly()
print("Rows:", len(df), "| from", df.index.min(), "to", df.index.max())
print("\n== Descriptive statistics ==")
print(df.describe().T[["mean", "std", "min", "max"]].round(2))
# ---- 1. Grouping by time: monthly climate profile
monthly = df.resample("MS").agg({"air_temp_c": "mean", "air_humidity_pct": "mean",
"precipitation_mm": "sum", "et0_mm": "sum",
"soil_moisture_0_7cm": "mean"}).round(2)
monthly["water_balance_mm"] = (monthly["precipitation_mm"] - monthly["et0_mm"]).round(1)
print("\n== Monthly profile ==")
print(monthly.head(12))
# ---- 2. Diurnal cycle: when is it hottest? (helps choose irrigation time)
diurnal = df.groupby(df.index.hour)[["air_temp_c", "air_humidity_pct", "et0_mm"]].mean().round(2)
print("\n== Hour of hottest air (mean):", int(diurnal["air_temp_c"].idxmax()), ":00")
# ---- 3. Weekly report for one season (summer 2024)
summer = df.loc["2024-06-01":"2024-08-31"]
weekly = summer.resample("W").agg(temp_mean=("air_temp_c", "mean"), temp_max=("air_temp_c", "max"),
rain=("precipitation_mm", "sum"), et0=("et0_mm", "sum"),
soil_mean=("soil_moisture_0_7cm", "mean"),
soil_min=("soil_moisture_0_7cm", "min")).round(3)
weekly.to_csv(OUT / "s13_weekly_report.csv")
# ---- 4. Drought periods: consecutive hours with soil moisture below the refill point
dry = df["soil_moisture_0_7cm"] < REFILL_POINT
block_id = (dry != dry.shift()).cumsum()
periods = (df[dry].groupby(block_id[dry])
.agg(start=("soil_moisture_0_7cm", lambda s: s.index.min()),
end=("soil_moisture_0_7cm", lambda s: s.index.max()),
hours=("soil_moisture_0_7cm", "size"),
min_vwc=("soil_moisture_0_7cm", "min")))
periods = periods[periods["hours"] >= 48].sort_values("hours", ascending=False)
periods.to_csv(OUT / "s13_drought_periods.csv", index=False)
print(f"\n== Drought periods (>=48 h below {REFILL_POINT} m3/m3): {len(periods)}")
print(periods.head(5).to_string(index=False))
# ---- 5. Figure
fig, ax = plt.subplots(3, 1, figsize=(11, 8), sharex=True)
daily = df.resample("D").mean(numeric_only=True)
ax[0].plot(daily.index, daily["air_temp_c"], color="#e07a1f"); ax[0].set_ylabel("Air temp °C")
ax[1].bar(daily.index, df["precipitation_mm"].resample("D").sum(), color="#2a6fdb"); ax[1].set_ylabel("Rain mm")
ax[2].plot(daily.index, daily["soil_moisture_0_7cm"], color="#13787b")
ax[2].axhline(REFILL_POINT, ls="--", color="crimson", label="refill point"); ax[2].legend()
ax[2].set_ylabel("Soil VWC m³/m³")
fig.suptitle("KFS farm - climate & soil overview (2023-2024)")
fig.tight_layout(); fig.savefig(OUT / "s13_overview.png", dpi=120)
print("\nSaved outputs to", OUT)
print("Correlation soil moisture vs ET0 (daily):",
np.round(daily["soil_moisture_0_7cm"].corr(df["et0_mm"].resample("D").sum()), 3))
تحليل بيانات عقدنا
25 دقيقةأسبوع رطوبة التربة من عقد المزرعة
بيانات farm.db (السيشنات 8–12) أو ملف التصدير CSV — مع زيارة سريعة للحقل الأكثر جفافاً للتحقق
- القراءة من قاعدة البيانات
استخدم الكود التالي لقراءة farm.db مباشرة في Pandas.
- لكل عقدة
متوسط وأدنى رطوبة تربة يومياً، وعدد الساعات تحت 30%.
- التحقق الميداني
اذهب للحقل صاحب أكثر ساعات جفاف: هل علامات الإجهاد ظاهرة (التفاف الأوراق ظهراً)؟ خذ عينة تربة باليد (اختبار الكرة).
- المقارنة
هل تتفق قراءات عقدتك مع اتجاه بيانات Open-Meteo لنفس الأيام؟ لماذا قد تختلف؟ (الري!)
| العقدة | متوسط الأسبوع % | أدنى قيمة % | ساعات < 30% | ملاحظة الحقل |
|---|---|---|---|---|
- اغسل يديك بعد التعامل مع التربة والأسمدة.
import sqlite3
con = sqlite3.connect("smart-farm/farm.db")
nodes = pd.read_sql("SELECT node_id, ts, soil_moisture_pct, air_temp_c FROM readings", con, parse_dates=["ts"])
hourly = nodes.set_index("ts").groupby("node_id")["soil_moisture_pct"].resample("h").mean().unstack(0)
report = pd.DataFrame({"mean": hourly.mean(), "min": hourly.min(), "hours_below_30": (hourly < 30).sum()}).round(1)
print(report)
التقرير الإحصائي
30 دقيقةهيكل التقرير (صفحتان)
- الهدف: سؤال زراعي واحد واضح (مثلاً: متى وكم يحتاج حقل الذرة للري في يونيو؟).
- البيانات: المصدر، الفترة، الدقة الزمنية، والقيود (رطوبة Open-Meteo غير مروية).
- النتائج: جدول شهري + رسم واحد + 3 أرقام مهمة.
- التفسير الزراعي: ماذا تعني الأرقام للمحصول والري؟
- التوصية: قرار عملي (موعد الري، الكمية التقريبية بـ م³/فدان).
احتياج الري لشهر يونيو
باستخدام ET0 الشهري لعام 2024 ومعامل محصول للذرة في منتصف الموسم Kc = 1.2، احسب الاحتياج المائي الصافي لشهر يونيو بالمليمتر، ثم بالمتر المكعب للفدان (4200 م²)، مع طرح المطر الفعال.
الحل (Solution)
june = df.loc["2024-06"]
etc_mm = june["et0_mm"].sum() * 1.2 # crop evapotranspiration ETc = ET0 x Kc
rain_mm = june["precipitation_mm"].sum()
net_mm = max(0, etc_mm - rain_mm)
print(f"ETc = {etc_mm:.0f} mm | rain = {rain_mm:.1f} mm | net irrigation = {net_mm:.0f} mm")
print(f"= {net_mm * 4.2:.0f} m3 per feddan (before irrigation efficiency losses)")
اختبر فهمك
5 أسئلة · نتيجة فوريةالواجب والتسليم
الواجب الأسبوعي (Weekly Assignment)
أكمل التقرير الإحصائي (صفحتان) وارفعه PDF مع الـ Notebook.
حل تحدي احتياج يونيو، وكرره لشهر يناير — ما الفرق ولماذا؟
اختياري: حمّل بيانات Open-Meteo لعام 2022 من رابط الـ API وقارن صيف 2022 بصيف 2024.
قائمة التسليم (Submission Checklist)
مراجع ومصادر
استعد للسيشن القادمة
السيشن القادمة ننظف بيانات حقيقية «متسخة»: قيم مفقودة، أخطاء حساسات، انقطاع شبكة، ورسائل مكررة. حمّل node_log_raw.csv.