khaled-dsml 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- khaled_dsml/__init__.py +17 -0
- khaled_dsml/_results.py +39 -0
- khaled_dsml/bigdata.py +107 -0
- khaled_dsml/cli.py +36 -0
- khaled_dsml/distributed.py +32 -0
- khaled_dsml/econometrics.py +79 -0
- khaled_dsml/ml.py +155 -0
- khaled_dsml/plots.py +41 -0
- khaled_dsml/quality.py +109 -0
- khaled_dsml/quizzes.py +62 -0
- khaled_dsml/sqltools.py +35 -0
- khaled_dsml/statistics.py +162 -0
- khaled_dsml/timeseries.py +22 -0
- khaled_dsml-0.1.0.dist-info/METADATA +188 -0
- khaled_dsml-0.1.0.dist-info/RECORD +19 -0
- khaled_dsml-0.1.0.dist-info/WHEEL +5 -0
- khaled_dsml-0.1.0.dist-info/entry_points.txt +2 -0
- khaled_dsml-0.1.0.dist-info/licenses/LICENSE +21 -0
- khaled_dsml-0.1.0.dist-info/top_level.txt +1 -0
khaled_dsml/__init__.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""KhaledDSML: educational statistics, ML and big data in Python."""
|
|
2
|
+
__version__ = "0.1.0"
|
|
3
|
+
|
|
4
|
+
from .statistics import (normality_test, shapiro_test, jarque_bera_test,
|
|
5
|
+
independent_t_test, paired_t_test, mann_whitney_test, wilcoxon_test,
|
|
6
|
+
anova_test, kruskal_test, levene_test, chi_square_test, pearson_test,
|
|
7
|
+
spearman_test, ks_two_sample_test, descriptive_summary)
|
|
8
|
+
from .quality import (quality_report, missingness_report, iqr_outliers,
|
|
9
|
+
zscore_outliers, fill_missing_numeric, memory_report, optimize_memory,
|
|
10
|
+
distribution_drift_test)
|
|
11
|
+
from .ml import (split_data, make_regressor, make_classifier, regression_metrics,
|
|
12
|
+
classification_metrics, cross_validation_summary, cluster_data, pca_summary)
|
|
13
|
+
from .bigdata import (generate_transactions, csv_chunk_summary, csv_chunk_missing,
|
|
14
|
+
csv_chunk_monthly, map_reduce_word_count, json_documents_to_table)
|
|
15
|
+
from .quizzes import Question, get_questions, grade_quiz
|
|
16
|
+
from .timeseries import lagged_features
|
|
17
|
+
from .sqltools import sqlite_import_frame, sqlite_group_summary
|
khaled_dsml/_results.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Simple, teaching-oriented results for statistical hypothesis tests."""
|
|
2
|
+
from dataclasses import asdict, dataclass
|
|
3
|
+
import math
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass(frozen=True)
|
|
7
|
+
class TestResult:
|
|
8
|
+
test: str
|
|
9
|
+
statistic: float
|
|
10
|
+
p_value: float
|
|
11
|
+
alpha: float
|
|
12
|
+
reject_null: bool
|
|
13
|
+
interpretation_ar: str
|
|
14
|
+
null_hypothesis_ar: str
|
|
15
|
+
sample_size: int
|
|
16
|
+
notes: str = ""
|
|
17
|
+
|
|
18
|
+
def to_dict(self) -> dict:
|
|
19
|
+
return asdict(self)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def make_result(
|
|
23
|
+
name: str, statistic: float, p_value: float, n: int,
|
|
24
|
+
alpha: float, null_ar: str, notes: str = "",
|
|
25
|
+
) -> TestResult:
|
|
26
|
+
if not (0 < alpha < 1):
|
|
27
|
+
raise ValueError("alpha must be between 0 and 1 (exclusive)")
|
|
28
|
+
if not math.isfinite(float(p_value)) or not (0 <= float(p_value) <= 1):
|
|
29
|
+
raise ValueError("Test returned an invalid p-value; inspect assumptions and data")
|
|
30
|
+
reject = bool(p_value < alpha)
|
|
31
|
+
interpretation = (
|
|
32
|
+
f"نرفض الفرضية الصفرية عند مستوى دلالة {alpha:g}؛ "
|
|
33
|
+
"توجد أدلة إحصائية ضدها."
|
|
34
|
+
if reject else
|
|
35
|
+
f"لا نرفض الفرضية الصفرية عند مستوى دلالة {alpha:g}؛ "
|
|
36
|
+
"لا يعني ذلك إثبات صحتها."
|
|
37
|
+
)
|
|
38
|
+
return TestResult(name, float(statistic), float(p_value), float(alpha),
|
|
39
|
+
reject, interpretation, null_ar, int(n), notes)
|
khaled_dsml/bigdata.py
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Memory-aware big-data teaching tools (local, chunked, non-distributed)."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from collections import Counter
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import re
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
import pandas as pd
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def generate_transactions(n_rows=10_000, seed=42, missing_rate=.02) -> pd.DataFrame:
|
|
13
|
+
"""Reproducible synthetic Algerian transactions, amounts in Algerian dinars."""
|
|
14
|
+
if n_rows < 1 or not 0 <= missing_rate < 1:
|
|
15
|
+
raise ValueError("Require n_rows >= 1 and 0 <= missing_rate < 1")
|
|
16
|
+
rng = np.random.default_rng(seed)
|
|
17
|
+
base = pd.Timestamp("2025-01-01")
|
|
18
|
+
df = pd.DataFrame({
|
|
19
|
+
"id_client": rng.integers(1, 50_000, n_rows),
|
|
20
|
+
"wilaya": rng.choice(["Alger", "Oran", "Setif", "Constantine", "Annaba", "Batna"], n_rows),
|
|
21
|
+
"categorie": rng.choice(["Alimentation", "Electronique", "Vetements", "Transport"], n_rows),
|
|
22
|
+
"montant_da": np.round(rng.lognormal(7.5, .9, n_rows), 2),
|
|
23
|
+
"date": base + pd.to_timedelta(rng.integers(0, 365*86400, n_rows), unit="s"),
|
|
24
|
+
})
|
|
25
|
+
miss = rng.random(n_rows) < missing_rate
|
|
26
|
+
df.loc[miss, "montant_da"] = np.nan
|
|
27
|
+
return df
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def csv_chunk_summary(path: str | Path, *, amount_column="montant_da",
|
|
31
|
+
group_by="wilaya", chunksize=100_000) -> pd.DataFrame:
|
|
32
|
+
"""Streaming count/sum/mean by category; never loads the whole CSV."""
|
|
33
|
+
if chunksize < 1:
|
|
34
|
+
raise ValueError("chunksize must be positive")
|
|
35
|
+
usecols = [amount_column] if group_by is None else [group_by, amount_column]
|
|
36
|
+
sums: dict = {}
|
|
37
|
+
counts: dict = {}
|
|
38
|
+
for chunk in pd.read_csv(path, usecols=usecols, chunksize=chunksize):
|
|
39
|
+
chunk[amount_column] = pd.to_numeric(chunk[amount_column], errors="coerce")
|
|
40
|
+
if group_by is None:
|
|
41
|
+
pairs = [("ALL", (int(chunk[amount_column].count()),
|
|
42
|
+
float(chunk[amount_column].sum())))]
|
|
43
|
+
else:
|
|
44
|
+
group = chunk.groupby(group_by, dropna=False)[amount_column].agg(["count", "sum"])
|
|
45
|
+
pairs = [(k, (int(row["count"]), float(row["sum"]))) for k, row in group.iterrows()]
|
|
46
|
+
for key, (count, total) in pairs:
|
|
47
|
+
# Use a stable string key also for missing group values.
|
|
48
|
+
k = "<MISSING>" if pd.isna(key) else str(key)
|
|
49
|
+
sums[k] = sums.get(k, 0.0) + total
|
|
50
|
+
counts[k] = counts.get(k, 0) + count
|
|
51
|
+
result = pd.DataFrame({"group": list(sums),
|
|
52
|
+
"count": [counts[k] for k in sums],
|
|
53
|
+
"sum": [sums[k] for k in sums]})
|
|
54
|
+
if result.empty:
|
|
55
|
+
return pd.DataFrame(columns=["count", "sum", "mean"],
|
|
56
|
+
index=pd.Index([], name=group_by or "group"))
|
|
57
|
+
result["mean"] = result["sum"] / result["count"].replace(0, np.nan)
|
|
58
|
+
return result.set_index("group").rename_axis(group_by or "group").sort_values("sum", ascending=False)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def csv_chunk_missing(path: str | Path, *, chunksize=100_000) -> pd.DataFrame:
|
|
62
|
+
"""Count missing observations over all CSV chunks."""
|
|
63
|
+
if chunksize < 1:
|
|
64
|
+
raise ValueError("chunksize must be positive")
|
|
65
|
+
total = 0
|
|
66
|
+
missing = None
|
|
67
|
+
for chunk in pd.read_csv(path, chunksize=chunksize):
|
|
68
|
+
total += len(chunk)
|
|
69
|
+
counts = chunk.isna().sum()
|
|
70
|
+
missing = counts if missing is None else missing.add(counts, fill_value=0)
|
|
71
|
+
if missing is None:
|
|
72
|
+
return pd.DataFrame(columns=["count", "percent"])
|
|
73
|
+
return pd.DataFrame({"count": missing.astype(int), "percent": missing / total * 100})
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def csv_chunk_monthly(path: str | Path, *, date_column="date",
|
|
77
|
+
amount_column="montant_da", chunksize=100_000) -> pd.Series:
|
|
78
|
+
if chunksize < 1:
|
|
79
|
+
raise ValueError("chunksize must be positive")
|
|
80
|
+
sums = {}
|
|
81
|
+
for chunk in pd.read_csv(path, usecols=[date_column, amount_column], chunksize=chunksize):
|
|
82
|
+
dates = pd.to_datetime(chunk[date_column], errors="coerce")
|
|
83
|
+
amounts = pd.to_numeric(chunk[amount_column], errors="coerce")
|
|
84
|
+
grouped = amounts.groupby(dates.dt.to_period("M")).sum()
|
|
85
|
+
for period, value in grouped.items():
|
|
86
|
+
if pd.notna(period):
|
|
87
|
+
sums[period] = sums.get(period, 0.0) + float(value)
|
|
88
|
+
result = pd.Series(sums, dtype=float, name="total_da").sort_index()
|
|
89
|
+
result.index.name = "month"
|
|
90
|
+
return result
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def map_reduce_word_count(documents, *, min_length=2) -> dict[str, int]:
|
|
94
|
+
"""Small local MapReduce illustration, not an Apache Spark implementation."""
|
|
95
|
+
if min_length < 1:
|
|
96
|
+
raise ValueError("min_length must be >= 1")
|
|
97
|
+
mapped = (Counter(w.lower() for w in re.findall(r"\b\w+\b", str(doc), flags=re.UNICODE)
|
|
98
|
+
if len(w) >= min_length) for doc in documents)
|
|
99
|
+
reduced: Counter = Counter()
|
|
100
|
+
for part in mapped:
|
|
101
|
+
reduced.update(part)
|
|
102
|
+
return dict(reduced.most_common())
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def json_documents_to_table(documents: list[dict]) -> pd.DataFrame:
|
|
106
|
+
"""Flatten document-style JSON as an introduction to NoSQL data."""
|
|
107
|
+
return pd.json_normalize(documents)
|
khaled_dsml/cli.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Command line interface: demo, inspect, and quiz."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import json
|
|
6
|
+
from .bigdata import generate_transactions
|
|
7
|
+
from .quality import quality_report
|
|
8
|
+
from .quizzes import get_questions
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def main(argv=None):
|
|
12
|
+
parser = argparse.ArgumentParser(prog="khaled-dsml", description="KhaledDSML educational toolkit")
|
|
13
|
+
commands = parser.add_subparsers(dest="command", required=True)
|
|
14
|
+
demo = commands.add_parser("demo", help="Run a synthetic dataset example")
|
|
15
|
+
demo.add_argument("--rows", type=int, default=1000)
|
|
16
|
+
inspect = commands.add_parser("inspect", help="Inspect a CSV file (loads it into RAM)")
|
|
17
|
+
inspect.add_argument("csv")
|
|
18
|
+
quiz = commands.add_parser("quiz", help="Print questions without exposing the solutions")
|
|
19
|
+
quiz.add_argument("--course", choices=["all", "data_science", "machine_learning", "big_data"], default="all")
|
|
20
|
+
quiz.add_argument("--count", type=int, default=5)
|
|
21
|
+
args = parser.parse_args(argv)
|
|
22
|
+
if args.command == "demo":
|
|
23
|
+
df = generate_transactions(n_rows=args.rows)
|
|
24
|
+
print(json.dumps(quality_report(df), ensure_ascii=False, indent=2))
|
|
25
|
+
elif args.command == "inspect":
|
|
26
|
+
import pandas as pd
|
|
27
|
+
print(json.dumps(quality_report(pd.read_csv(args.csv)), ensure_ascii=False, indent=2))
|
|
28
|
+
else:
|
|
29
|
+
for q in get_questions(args.course, args.count):
|
|
30
|
+
print(f"\n{q.id}: {q.question}")
|
|
31
|
+
for label, choice in zip("ABCD", q.choices):
|
|
32
|
+
print(f" {label}. {choice}")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
if __name__ == "__main__":
|
|
36
|
+
main()
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Optional adapters for Dask and Spark; real engines required when used."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def dask_csv_summary(path, *, group_by="wilaya", amount_column="montant_da",
|
|
6
|
+
blocksize="64MB"):
|
|
7
|
+
"""Lazy distributed CSV computation; returns a small pandas result table."""
|
|
8
|
+
try:
|
|
9
|
+
import dask.dataframe as dd
|
|
10
|
+
except ImportError as exc:
|
|
11
|
+
raise ImportError('Install with: pip install "khaled-dsml[dask]"') from exc
|
|
12
|
+
frame = dd.read_csv(str(path), blocksize=blocksize)
|
|
13
|
+
result = frame.groupby(group_by)[amount_column].agg(["count", "sum", "mean"])
|
|
14
|
+
return result.compute().sort_values("sum", ascending=False)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def spark_group_summary(spark_df, *, group_by="wilaya", amount_column="montant_da"):
|
|
18
|
+
"""Return aggregated *Spark DataFrame* without collecting it to driver RAM."""
|
|
19
|
+
try:
|
|
20
|
+
from pyspark.sql import functions as F
|
|
21
|
+
from pyspark.sql import DataFrame as SparkDataFrame
|
|
22
|
+
except ImportError as exc:
|
|
23
|
+
raise ImportError('Install with: pip install "khaled-dsml[spark]"; Java is also required') from exc
|
|
24
|
+
if not isinstance(spark_df, SparkDataFrame):
|
|
25
|
+
raise TypeError("spark_df must be a pyspark.sql.DataFrame")
|
|
26
|
+
if group_by not in spark_df.columns or amount_column not in spark_df.columns:
|
|
27
|
+
raise KeyError("Requested columns are absent")
|
|
28
|
+
return spark_df.groupBy(group_by).agg(
|
|
29
|
+
F.count(amount_column).alias("count"),
|
|
30
|
+
F.sum(amount_column).alias("sum"),
|
|
31
|
+
F.avg(amount_column).alias("mean"),
|
|
32
|
+
).orderBy(F.col("sum").desc())
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Optional statsmodels diagnostics for quantitative economics."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from ._results import make_result, TestResult
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _require_statsmodels():
|
|
10
|
+
try:
|
|
11
|
+
import statsmodels.api as sm
|
|
12
|
+
except ImportError as exc:
|
|
13
|
+
raise ImportError('Install with: pip install "khaled-dsml[econometrics]"') from exc
|
|
14
|
+
return sm
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def adf_test(series, alpha=0.05) -> TestResult:
|
|
18
|
+
sm = _require_statsmodels()
|
|
19
|
+
from statsmodels.tsa.stattools import adfuller
|
|
20
|
+
x = pd.to_numeric(pd.Series(series), errors="raise").dropna().to_numpy(dtype=float)
|
|
21
|
+
if len(x) < 10:
|
|
22
|
+
raise ValueError("Need at least 10 observations for this educational ADF check")
|
|
23
|
+
stat, p, *_ = adfuller(x, autolag="AIC")
|
|
24
|
+
return make_result("Augmented Dickey-Fuller", stat, p, len(x), alpha,
|
|
25
|
+
"توجد جذور وحدة (السلسلة غير مستقرة بالمعنى المختبر).",
|
|
26
|
+
"اختيار المكون الحتمي وطول الإبطاء يؤثر في الاستنتاج.")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def vif_table(frame: pd.DataFrame) -> pd.DataFrame:
|
|
30
|
+
sm = _require_statsmodels()
|
|
31
|
+
from statsmodels.stats.outliers_influence import variance_inflation_factor
|
|
32
|
+
if not isinstance(frame, pd.DataFrame) or frame.shape[1] < 2:
|
|
33
|
+
raise ValueError("Provide numeric DataFrame with at least 2 features")
|
|
34
|
+
arr = frame.to_numpy(dtype=float)
|
|
35
|
+
if not np.all(np.isfinite(arr)):
|
|
36
|
+
raise ValueError("VIF requires complete finite numeric data")
|
|
37
|
+
design = sm.add_constant(arr, has_constant="add")
|
|
38
|
+
values = [float(variance_inflation_factor(design, i + 1)) for i in range(arr.shape[1])]
|
|
39
|
+
return pd.DataFrame({"feature": frame.columns.tolist(), "vif": values})
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def breusch_pagan_test(residuals, features, alpha=.05) -> TestResult:
|
|
43
|
+
sm = _require_statsmodels()
|
|
44
|
+
from statsmodels.stats.diagnostic import het_breuschpagan
|
|
45
|
+
residuals = np.asarray(residuals, dtype=float)
|
|
46
|
+
x = np.asarray(features, dtype=float)
|
|
47
|
+
if x.ndim != 2 or len(residuals) != len(x) or not np.all(np.isfinite(x)):
|
|
48
|
+
raise ValueError("Features must be a finite 2D matrix aligned with residuals")
|
|
49
|
+
if x.shape[1] < 1 or len(x) <= x.shape[1] + 1:
|
|
50
|
+
raise ValueError("Insufficient observations for Breusch-Pagan test")
|
|
51
|
+
design = sm.add_constant(x, has_constant="add")
|
|
52
|
+
stat, p, *_ = het_breuschpagan(residuals, design)
|
|
53
|
+
return make_result("Breusch-Pagan", stat, p, len(residuals), alpha,
|
|
54
|
+
"تباين الأخطاء ثابت.")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def ljung_box_test(series, lags=10, alpha=0.05) -> TestResult:
|
|
58
|
+
sm = _require_statsmodels()
|
|
59
|
+
from statsmodels.stats.diagnostic import acorr_ljungbox
|
|
60
|
+
x = pd.to_numeric(pd.Series(series), errors="raise").dropna().to_numpy(dtype=float)
|
|
61
|
+
if lags < 1 or len(x) <= lags:
|
|
62
|
+
raise ValueError("Need 1 <= lags < number of observations")
|
|
63
|
+
r = acorr_ljungbox(x, lags=[lags], return_df=True)
|
|
64
|
+
return make_result("Ljung-Box", r["lb_stat"].iloc[0], r["lb_pvalue"].iloc[0],
|
|
65
|
+
len(x), alpha, "لا يوجد ارتباط ذاتي حتى عدد الإبطاءات المختار.")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def cronbach_alpha(items: pd.DataFrame) -> float:
|
|
69
|
+
"""Internal-consistency coefficient; no claim of unidimensionality."""
|
|
70
|
+
if not isinstance(items, pd.DataFrame) or items.shape[1] < 2:
|
|
71
|
+
raise ValueError("Need >=2 item columns")
|
|
72
|
+
x = items.astype(float).dropna().to_numpy()
|
|
73
|
+
if len(x) < 2:
|
|
74
|
+
raise ValueError("Need >=2 complete cases")
|
|
75
|
+
k = x.shape[1]
|
|
76
|
+
total = np.var(x.sum(axis=1), ddof=1)
|
|
77
|
+
if total <= 0:
|
|
78
|
+
raise ValueError("Total-score variance is zero")
|
|
79
|
+
return float((k / (k - 1)) * (1 - np.var(x, axis=0, ddof=1).sum() / total))
|
khaled_dsml/ml.py
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""Leakage-aware ML workflows and metrics for classroom use."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pandas as pd
|
|
7
|
+
from sklearn.cluster import AgglomerativeClustering, KMeans
|
|
8
|
+
from sklearn.compose import ColumnTransformer
|
|
9
|
+
from sklearn.decomposition import PCA
|
|
10
|
+
from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor
|
|
11
|
+
from sklearn.impute import SimpleImputer
|
|
12
|
+
from sklearn.linear_model import Lasso, LinearRegression, LogisticRegression, Ridge
|
|
13
|
+
from sklearn.metrics import (accuracy_score, confusion_matrix, f1_score,
|
|
14
|
+
mean_absolute_error, mean_squared_error, precision_score, recall_score,
|
|
15
|
+
r2_score, silhouette_score, davies_bouldin_score)
|
|
16
|
+
from sklearn.model_selection import KFold, StratifiedKFold, cross_val_score, train_test_split
|
|
17
|
+
from sklearn.neighbors import KNeighborsClassifier
|
|
18
|
+
from sklearn.pipeline import Pipeline
|
|
19
|
+
from sklearn.preprocessing import OneHotEncoder, StandardScaler
|
|
20
|
+
from sklearn.tree import DecisionTreeClassifier, DecisionTreeRegressor
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def split_data(X, y, *, test_size=.2, random_state=42, stratify=False):
|
|
24
|
+
"""Always split before computing any preprocessing statistics."""
|
|
25
|
+
return train_test_split(X, y, test_size=test_size, random_state=random_state,
|
|
26
|
+
stratify=y if stratify else None)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _preprocessor(X):
|
|
30
|
+
if isinstance(X, pd.DataFrame):
|
|
31
|
+
numeric = X.select_dtypes(include="number").columns.tolist()
|
|
32
|
+
categorical = [c for c in X.columns if c not in numeric]
|
|
33
|
+
transformers = []
|
|
34
|
+
if numeric:
|
|
35
|
+
transformers.append(("numeric", Pipeline([
|
|
36
|
+
("impute", SimpleImputer(strategy="median")),
|
|
37
|
+
("scale", StandardScaler()),
|
|
38
|
+
]), numeric))
|
|
39
|
+
if categorical:
|
|
40
|
+
transformers.append(("categorical", Pipeline([
|
|
41
|
+
("impute", SimpleImputer(strategy="most_frequent")),
|
|
42
|
+
("encode", OneHotEncoder(handle_unknown="ignore")),
|
|
43
|
+
]), categorical))
|
|
44
|
+
if not transformers:
|
|
45
|
+
raise ValueError("X must contain at least one feature")
|
|
46
|
+
return ColumnTransformer(transformers)
|
|
47
|
+
return Pipeline([("impute", SimpleImputer(strategy="median")),
|
|
48
|
+
("scale", StandardScaler())])
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def make_regressor(X, algorithm="linear", random_state=42, **kwargs) -> Pipeline:
|
|
52
|
+
"""Unfitted pipeline. Fit on training data only."""
|
|
53
|
+
algorithms = {
|
|
54
|
+
"linear": LinearRegression,
|
|
55
|
+
"ridge": Ridge,
|
|
56
|
+
"lasso": Lasso,
|
|
57
|
+
"decision_tree": DecisionTreeRegressor,
|
|
58
|
+
"random_forest": RandomForestRegressor,
|
|
59
|
+
}
|
|
60
|
+
if algorithm not in algorithms:
|
|
61
|
+
raise ValueError("algorithm must be: " + ", ".join(algorithms))
|
|
62
|
+
cls = algorithms[algorithm]
|
|
63
|
+
if algorithm in ("decision_tree", "random_forest"):
|
|
64
|
+
kwargs.setdefault("random_state", random_state)
|
|
65
|
+
return Pipeline([("preprocess", _preprocessor(X)), ("model", cls(**kwargs))])
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def make_classifier(X, algorithm="logistic", random_state=42, **kwargs) -> Pipeline:
|
|
69
|
+
"""Unfitted pipeline with imputation and categoricals inside CV folds."""
|
|
70
|
+
algorithms = {
|
|
71
|
+
"logistic": LogisticRegression,
|
|
72
|
+
"knn": KNeighborsClassifier,
|
|
73
|
+
"decision_tree": DecisionTreeClassifier,
|
|
74
|
+
"random_forest": RandomForestClassifier,
|
|
75
|
+
}
|
|
76
|
+
if algorithm not in algorithms:
|
|
77
|
+
raise ValueError("algorithm must be: " + ", ".join(algorithms))
|
|
78
|
+
cls = algorithms[algorithm]
|
|
79
|
+
if algorithm in ("logistic", "decision_tree", "random_forest"):
|
|
80
|
+
kwargs.setdefault("random_state", random_state)
|
|
81
|
+
if algorithm == "logistic":
|
|
82
|
+
kwargs.setdefault("max_iter", 1000)
|
|
83
|
+
return Pipeline([("preprocess", _preprocessor(X)), ("model", cls(**kwargs))])
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def regression_metrics(y_true, y_pred) -> dict:
|
|
87
|
+
return {"mae": float(mean_absolute_error(y_true, y_pred)),
|
|
88
|
+
"mse": float(mean_squared_error(y_true, y_pred)),
|
|
89
|
+
"rmse": float(np.sqrt(mean_squared_error(y_true, y_pred))),
|
|
90
|
+
"r2": float(r2_score(y_true, y_pred))}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def classification_metrics(y_true, y_pred) -> dict:
|
|
94
|
+
"""Macro averaging treats every class equally, including minority classes."""
|
|
95
|
+
return {"accuracy": float(accuracy_score(y_true, y_pred)),
|
|
96
|
+
"precision_macro": float(precision_score(y_true, y_pred, average="macro", zero_division=0)),
|
|
97
|
+
"recall_macro": float(recall_score(y_true, y_pred, average="macro", zero_division=0)),
|
|
98
|
+
"f1_macro": float(f1_score(y_true, y_pred, average="macro", zero_division=0)),
|
|
99
|
+
"confusion_matrix": confusion_matrix(y_true, y_pred).tolist()}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def cross_validation_summary(estimator, X, y, *, task="regression", folds=5,
|
|
103
|
+
random_state=42, scoring=None) -> dict:
|
|
104
|
+
if folds < 2:
|
|
105
|
+
raise ValueError("folds must be >= 2")
|
|
106
|
+
if task not in ("regression", "classification"):
|
|
107
|
+
raise ValueError("task must be regression or classification")
|
|
108
|
+
cv = (KFold(n_splits=folds, shuffle=True, random_state=random_state)
|
|
109
|
+
if task == "regression" else
|
|
110
|
+
StratifiedKFold(n_splits=folds, shuffle=True, random_state=random_state))
|
|
111
|
+
score = scoring or ("r2" if task == "regression" else "f1_macro")
|
|
112
|
+
values = cross_val_score(estimator, X, y, cv=cv, scoring=score)
|
|
113
|
+
return {"scoring": score, "fold_scores": values.tolist(),
|
|
114
|
+
"mean": float(np.mean(values)), "std": float(np.std(values))}
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@dataclass
|
|
118
|
+
class ClusterResult:
|
|
119
|
+
labels: np.ndarray
|
|
120
|
+
silhouette: float | None
|
|
121
|
+
davies_bouldin: float | None
|
|
122
|
+
model: object
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def cluster_data(X, *, method="kmeans", n_clusters=3, random_state=42,
|
|
126
|
+
scale=True) -> ClusterResult:
|
|
127
|
+
x = np.asarray(X, dtype=float)
|
|
128
|
+
if x.ndim != 2 or len(x) < 2 or not np.all(np.isfinite(x)):
|
|
129
|
+
raise ValueError("Clustering requires a finite numeric 2D matrix")
|
|
130
|
+
if not 2 <= n_clusters <= len(x):
|
|
131
|
+
raise ValueError("Need 2 <= n_clusters <= number of rows")
|
|
132
|
+
data = StandardScaler().fit_transform(x) if scale else x
|
|
133
|
+
if method == "kmeans":
|
|
134
|
+
model = KMeans(n_clusters=n_clusters, random_state=random_state, n_init=10)
|
|
135
|
+
elif method == "hierarchical":
|
|
136
|
+
model = AgglomerativeClustering(n_clusters=n_clusters)
|
|
137
|
+
else:
|
|
138
|
+
raise ValueError("method must be kmeans or hierarchical")
|
|
139
|
+
labels = model.fit_predict(data)
|
|
140
|
+
n_unique = len(np.unique(labels))
|
|
141
|
+
silhouette = (float(silhouette_score(data, labels))
|
|
142
|
+
if 2 <= n_unique < len(x) else None)
|
|
143
|
+
db = float(davies_bouldin_score(data, labels)) if 2 <= n_unique < len(x) else None
|
|
144
|
+
return ClusterResult(labels, silhouette, db, model)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def pca_summary(X, n_components=2, *, scale=True) -> dict:
|
|
148
|
+
x = np.asarray(X, dtype=float)
|
|
149
|
+
if x.ndim != 2 or not np.all(np.isfinite(x)):
|
|
150
|
+
raise ValueError("PCA needs complete finite numeric 2D data")
|
|
151
|
+
data = StandardScaler().fit_transform(x) if scale else x
|
|
152
|
+
model = PCA(n_components=n_components)
|
|
153
|
+
components = model.fit_transform(data)
|
|
154
|
+
return {"scores": components, "explained_variance_ratio": model.explained_variance_ratio_.tolist(),
|
|
155
|
+
"components": model.components_, "model": model}
|
khaled_dsml/plots.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Optional classroom figures; matplotlib is installed through [plots]."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def _plt():
|
|
6
|
+
try:
|
|
7
|
+
import matplotlib.pyplot as plt
|
|
8
|
+
except ImportError as exc:
|
|
9
|
+
raise ImportError('Install with: pip install "khaled-dsml[plots]"') from exc
|
|
10
|
+
return plt
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def plot_missingness(df):
|
|
14
|
+
plt = _plt()
|
|
15
|
+
fig, ax = plt.subplots()
|
|
16
|
+
(100 * df.isna().mean()).plot.bar(ax=ax)
|
|
17
|
+
ax.set(ylabel="Missing values (%)", title="Missingness by column")
|
|
18
|
+
fig.tight_layout()
|
|
19
|
+
return fig, ax
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def plot_monthly(monthly):
|
|
23
|
+
plt = _plt()
|
|
24
|
+
fig, ax = plt.subplots()
|
|
25
|
+
monthly.plot(ax=ax)
|
|
26
|
+
ax.set(ylabel="Total (DA)", title="Monthly transaction amounts")
|
|
27
|
+
fig.tight_layout()
|
|
28
|
+
return fig, ax
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def plot_confusion(confusion):
|
|
32
|
+
import numpy as np
|
|
33
|
+
plt = _plt()
|
|
34
|
+
cm = np.asarray(confusion)
|
|
35
|
+
fig, ax = plt.subplots()
|
|
36
|
+
ax.imshow(cm)
|
|
37
|
+
for (i, j), value in np.ndenumerate(cm):
|
|
38
|
+
ax.text(j, i, str(value), ha="center", va="center")
|
|
39
|
+
ax.set(xlabel="Predicted", ylabel="Actual", title="Confusion matrix")
|
|
40
|
+
fig.tight_layout()
|
|
41
|
+
return fig, ax
|
khaled_dsml/quality.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Data inspection, cleaning helpers and simple distribution drift diagnostics."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from scipy import stats
|
|
7
|
+
from ._results import make_result, TestResult
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def quality_report(df: pd.DataFrame) -> dict:
|
|
11
|
+
"""Return shape, missing cells, duplicate rows, dtypes and memory usage."""
|
|
12
|
+
_require_frame(df)
|
|
13
|
+
return {
|
|
14
|
+
"rows": len(df), "columns": len(df.columns),
|
|
15
|
+
"missing_cells": int(df.isna().sum().sum()),
|
|
16
|
+
"missing_by_column": df.isna().sum().astype(int).to_dict(),
|
|
17
|
+
"duplicate_rows": int(df.duplicated().sum()),
|
|
18
|
+
"dtypes": {k: str(v) for k, v in df.dtypes.items()},
|
|
19
|
+
"memory_mb": round(float(df.memory_usage(deep=True).sum()) / 1e6, 4),
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def missingness_report(df: pd.DataFrame) -> pd.DataFrame:
|
|
24
|
+
_require_frame(df)
|
|
25
|
+
result = pd.DataFrame({"count": df.isna().sum()})
|
|
26
|
+
result["percent"] = (100 * result["count"] / len(df)) if len(df) else 0.0
|
|
27
|
+
return result
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _require_frame(df):
|
|
31
|
+
if not isinstance(df, pd.DataFrame):
|
|
32
|
+
raise TypeError("Expected a pandas DataFrame")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def iqr_outliers(data: pd.DataFrame, column: str, multiplier: float = 1.5) -> pd.Series:
|
|
36
|
+
"""Boolean mask of outliers; NaNs are False. Returns original row index."""
|
|
37
|
+
_require_frame(data)
|
|
38
|
+
if multiplier <= 0:
|
|
39
|
+
raise ValueError("multiplier must be positive")
|
|
40
|
+
x = pd.to_numeric(data[column], errors="raise")
|
|
41
|
+
if x.notna().sum() == 0:
|
|
42
|
+
raise ValueError("Column has no observed values")
|
|
43
|
+
q1, q3 = x.quantile([.25, .75])
|
|
44
|
+
spread = q3 - q1
|
|
45
|
+
return ((x < q1 - multiplier * spread) | (x > q3 + multiplier * spread)).fillna(False)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def zscore_outliers(data: pd.DataFrame, column: str, threshold: float = 3.0) -> pd.Series:
|
|
49
|
+
_require_frame(data)
|
|
50
|
+
if threshold <= 0:
|
|
51
|
+
raise ValueError("threshold must be positive")
|
|
52
|
+
x = pd.to_numeric(data[column], errors="raise")
|
|
53
|
+
std = x.std(ddof=0)
|
|
54
|
+
if not pd.notna(std) or std == 0:
|
|
55
|
+
return pd.Series(False, index=data.index)
|
|
56
|
+
return ((x - x.mean()).abs() / std > threshold).fillna(False)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def fill_missing_numeric(df: pd.DataFrame, strategy: str = "median") -> pd.DataFrame:
|
|
60
|
+
"""Fill numeric missing values; return a copy. For ML use train-only imputation."""
|
|
61
|
+
_require_frame(df)
|
|
62
|
+
if strategy not in ("median", "mean", "zero"):
|
|
63
|
+
raise ValueError("strategy must be median, mean or zero")
|
|
64
|
+
out = df.copy()
|
|
65
|
+
for c in out.select_dtypes(include="number").columns:
|
|
66
|
+
if out[c].notna().sum() == 0:
|
|
67
|
+
continue
|
|
68
|
+
value = {"median": out[c].median, "mean": out[c].mean, "zero": lambda: 0}[strategy]()
|
|
69
|
+
out[c] = out[c].fillna(value)
|
|
70
|
+
return out
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def memory_report(df: pd.DataFrame) -> pd.DataFrame:
|
|
74
|
+
_require_frame(df)
|
|
75
|
+
usage = df.memory_usage(deep=True, index=False)
|
|
76
|
+
return pd.DataFrame({"bytes": usage, "megabytes": usage / 1e6}).sort_values("bytes", ascending=False)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def optimize_memory(df: pd.DataFrame, *, category_ratio: float = .5,
|
|
80
|
+
allow_float_downcast: bool = False) -> pd.DataFrame:
|
|
81
|
+
"""Downcast ints, optionally float64, and low-cardinality text columns.
|
|
82
|
+
|
|
83
|
+
Float downcasting can be lossy and is disabled by default.
|
|
84
|
+
Original dataframe is not changed.
|
|
85
|
+
"""
|
|
86
|
+
_require_frame(df)
|
|
87
|
+
if not 0 < category_ratio <= 1:
|
|
88
|
+
raise ValueError("category_ratio must be in (0, 1]")
|
|
89
|
+
out = df.copy()
|
|
90
|
+
for c in out.columns:
|
|
91
|
+
s = out[c]
|
|
92
|
+
if pd.api.types.is_integer_dtype(s.dtype):
|
|
93
|
+
out[c] = pd.to_numeric(s, downcast="integer")
|
|
94
|
+
elif allow_float_downcast and pd.api.types.is_float_dtype(s.dtype):
|
|
95
|
+
out[c] = pd.to_numeric(s, downcast="float")
|
|
96
|
+
elif (pd.api.types.is_object_dtype(s.dtype) or pd.api.types.is_string_dtype(s.dtype)):
|
|
97
|
+
observed = s.dropna()
|
|
98
|
+
if len(observed) and observed.nunique() / len(observed) <= category_ratio:
|
|
99
|
+
out[c] = s.astype("category")
|
|
100
|
+
return out
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def distribution_drift_test(reference, current, alpha=.05) -> TestResult:
|
|
104
|
+
from .statistics import ks_two_sample_test
|
|
105
|
+
result = ks_two_sample_test(reference, current, alpha=alpha)
|
|
106
|
+
return TestResult("Distribution drift / KS", result.statistic, result.p_value,
|
|
107
|
+
result.alpha, result.reject_null, result.interpretation_ar,
|
|
108
|
+
result.null_hypothesis_ar, result.sample_size,
|
|
109
|
+
"اختبار اختلاف التوزيعات وليس اختبارًا للسببية.")
|
khaled_dsml/quizzes.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Optional classroom multiple-choice quizzes with reproducible selection."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from random import Random
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass(frozen=True)
|
|
8
|
+
class Question:
|
|
9
|
+
id: str
|
|
10
|
+
course: str
|
|
11
|
+
question: str
|
|
12
|
+
choices: tuple[str, str, str, str]
|
|
13
|
+
answer: str
|
|
14
|
+
explanation_ar: str
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
BANK = [
|
|
18
|
+
Question("DS01", "data_science", "ما المقصود بوحدة المشاهدة؟", ("سطر يمثل حالة", "اسم الجدول", "نوع الرسم", "ملف البرنامج"), "A", "المشاهدة هي الحالة التي يمثلها صف الجدول."),
|
|
19
|
+
Question("DS02", "data_science", "أي مقياس أقل تأثرًا بالقيم المتطرفة؟", ("المتوسط", "الوسيط", "المدى", "التباين"), "B", "الوسيط مقاوم نسبيًا للقيم المتطرفة."),
|
|
20
|
+
Question("DS03", "data_science", "ما المشكلة الأساسية في حذف جميع القيم المفقودة؟", ("زيادة حجم البيانات", "فقد عينة واحتمال التحيز", "تحسين التمثيل دائمًا", "تقليل الدقة العددية فقط"), "B", "الحذف قد يقلص العينة ويسبب تحيزًا."),
|
|
21
|
+
Question("DS04", "data_science", "أي اختبار شائع لفحص اعتدالية توزيع البيانات؟", ("Shapiro-Wilk", "K-means", "F1", "MAE"), "A", "Shapiro-Wilk يختبر فرضية التوزيع الطبيعي."),
|
|
22
|
+
Question("DS05", "data_science", "ماذا يعني الارتباط الموجب؟", ("السببية مؤكدة", "اتجاه التغير المشترك غالبًا واحد", "عدم وجود علاقة", "تساوي المتوسطات"), "B", "الارتباط لا يثبت السببية."),
|
|
23
|
+
Question("DS06", "data_science", "ماذا يعني عدم رفض H0؟", ("أثبتنا صحة H0", "لا توجد أدلة كافية للرفض", "H1 صحيحة حتمًا", "حجم العينة كبير"), "B", "عدم الرفض لا يثبت الفرضية الصفرية."),
|
|
24
|
+
Question("DS07", "data_science", "الهدف من IQR في التنظيف؟", ("قياس ارتباط خطي", "كشف قيم بعيدة وفق الربيعات", "ترتيب الأشهر", "تقسيم التدريب"), "B", "قاعدة IQR إحدى طرق الإشارة إلى الشواذ."),
|
|
25
|
+
Question("DS08", "data_science", "ماذا تتطلب معالجة البيانات الأخلاقية؟", ("نشر بيانات العملاء", "حماية الخصوصية وتوثيق المصدر", "حذف مصدر البيانات", "إلغاء النسخ الاحتياطية"), "B", "إخفاء البيانات الحساسة وأخذ الموافقات من الأسس."),
|
|
26
|
+
Question("ML01", "machine_learning", "ما دور مجموعة الاختبار؟", ("ضبط كل المعاملات حتى أفضل نتيجة", "قياس التعميم النهائي", "تدريب النموذج", "حذف الفئات"), "B", "مجموعة الاختبار تعطي تقديرًا على بيانات غير مستخدمة بالتدريب/الضبط."),
|
|
27
|
+
Question("ML02", "machine_learning", "ما مثال على تسرب البيانات؟", ("التقييس قبل تقسيم كامل البيانات", "استخدام Pipeline داخل CV", "فصل البيانات أولًا", "تثبيت random_state"), "A", "التقييس قبل التقسيم ينقل معلومات الاختبار إلى التدريب."),
|
|
28
|
+
Question("ML03", "machine_learning", "أي مقياس يستخدم للتنبؤ العددي؟", ("Recall", "MAE", "Accuracy", "F1"), "B", "MAE متوسط الخطأ المطلق."),
|
|
29
|
+
Question("ML04", "machine_learning", "ما وظيفة Ridge؟", ("عقوبة L2", "تجميع غير خاضع للإشراف", "تشخيص بيانات نصية", "تحويل JSON"), "A", "Ridge يطبق تنظيم L2 في الانحدار."),
|
|
30
|
+
Question("ML05", "machine_learning", "أي خوارزمية تستخدم لتجميع عملاء دون تسميات؟", ("K-means", "LogisticRegression", "LinearRegression", "Welch t-test"), "A", "K-means تعلم غير خاضع للإشراف."),
|
|
31
|
+
Question("ML06", "machine_learning", "ما سبب استخدام F1 مع الفئات غير المتوازنة؟", ("يلغي تحيز البيانات", "يوفق بين Precision وRecall", "يقيس المبيعات", "يلغي الحاجة إلى الاختبار"), "B", "F1 متوسط توافقي للدقة الإيجابية والاسترجاع."),
|
|
32
|
+
Question("ML07", "machine_learning", "كيف تختبر عدة تقسيمات للتدريب والتقييم؟", ("Cross-validation", "Excel فقط", "إزالة المتغير الهدف", "تقريب الأرقام"), "A", "التحقق المتقاطع يعيد التقييم عبر طيات متعددة."),
|
|
33
|
+
Question("ML08", "machine_learning", "ما المقصود بـ PCA؟", ("اختبار قيمة مفقودة", "خفض الأبعاد بمركبات رئيسية", "تصنيف موجه", "توليد SQL"), "B", "PCA يستخرج مركبات تفسر تباينًا من البيانات."),
|
|
34
|
+
Question("BD01", "big_data", "ما المقصود بـ Volume في 5Vs؟", ("حجم البيانات", "دقتها", "مصدرها", "قيمتها الاقتصادية"), "A", "Volume يشير إلى حجم البيانات."),
|
|
35
|
+
Question("BD02", "big_data", "ماذا تفيد chunksize في read_csv؟", ("تحميل الملف كله دائمًا", "قراءة دفعات بدل الملف كاملًا", "إزالة الارتباط", "ترميز متغير الهدف"), "B", "chunksize يعيد أجزاء متعاقبة للمعالجة."),
|
|
36
|
+
Question("BD03", "big_data", "ما المقصود بـ MapReduce؟", ("رسم النقاط", "توزيع المعالجة ثم تجميع النتائج", "إعادة تسمية الأعمدة", "فحص الاعتدالية"), "B", "Map ثم Reduce من مفاهيم المعالجة الموزعة."),
|
|
37
|
+
Question("BD04", "big_data", "أي نوع تمثله مستندات JSON غالبًا؟", ("شبه منظمة", "صور خام", "غير قابلة للمعالجة", "بيانات مقاسة بالثواني"), "A", "JSON يستخدم بنية مفاتيح وقيم مرنة."),
|
|
38
|
+
Question("BD05", "big_data", "كيف نقارن استهلاك الذاكرة لـ DataFrame؟", ("memory_usage(deep=True)", "shape وحده", "head", "unique"), "A", "deep=True يحسب أيضًا الذاكرة المخزنة في كائنات النصوص."),
|
|
39
|
+
Question("BD06", "big_data", "ما الفرق بين SQL وNoSQL؟", ("NoSQL دائمًا أسرع", "NoSQL تدعم نماذج تخزين متعددة", "SQL لا تدعم الفهارس", "SQL تعني الصور"), "B", "NoSQL تشمل المستندات والمفاتيح/القيم وأشكالًا أخرى."),
|
|
40
|
+
Question("BD07", "big_data", "ما خطر استخلاص استنتاجات عامة من بيانات اصطناعية؟", ("لا توجد مخاطر", "قد لا تمثل الواقع الاقتصادي", "تزيد السرعة", "تزيد عدد الأعمدة"), "B", "بيانات المحاكاة توضح المنهج لكنها ليست دليلًا واقعيًا."),
|
|
41
|
+
Question("BD08", "big_data", "ما الاسم الشائع للتنبؤ الآني بمؤشر اقتصادي؟", ("Nowcasting", "OneHot", "Shapiro", "KNN"), "A", "Nowcasting تقدير للحالة الحالية قبل إصدار الإحصاءات النهائية."),
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def get_questions(course="all", limit=10, seed=42) -> list[Question]:
|
|
46
|
+
courses = ("all", "data_science", "machine_learning", "big_data")
|
|
47
|
+
if course not in courses:
|
|
48
|
+
raise ValueError("course must be: " + ", ".join(courses))
|
|
49
|
+
if limit < 1:
|
|
50
|
+
raise ValueError("limit must be positive")
|
|
51
|
+
questions = [q for q in BANK if course == "all" or q.course == course]
|
|
52
|
+
return Random(seed).sample(questions, min(limit, len(questions)))
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def grade_quiz(questions: list[Question], answers: dict[str, str]) -> dict:
|
|
56
|
+
"""Grade a list of questions. Answers map question IDs to A/B/C/D."""
|
|
57
|
+
results = [{"id": q.id, "correct": answers.get(q.id, "").strip().upper() == q.answer,
|
|
58
|
+
"answer": q.answer, "explanation_ar": q.explanation_ar} for q in questions]
|
|
59
|
+
count = sum(r["correct"] for r in results)
|
|
60
|
+
return {"correct": count, "total": len(results),
|
|
61
|
+
"percentage": round(count / len(results) * 100, 2) if results else 0,
|
|
62
|
+
"details": results}
|
khaled_dsml/sqltools.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""SQLite demonstrations for TD2: SQL versus document-style data."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
import re
|
|
4
|
+
import sqlite3
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import pandas as pd
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _safe_name(name: str) -> str:
|
|
10
|
+
if not isinstance(name, str) or not re.fullmatch(r"[A-Za-z_][A-Za-z0-9_]*", name):
|
|
11
|
+
raise ValueError("SQL identifier must be ASCII letters, digits or underscores")
|
|
12
|
+
return '"' + name + '"'
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def sqlite_import_frame(df: pd.DataFrame, database: str | Path,
|
|
16
|
+
table="transactions", if_exists="replace") -> int:
|
|
17
|
+
"""Store a pandas DataFrame in a local SQLite database."""
|
|
18
|
+
if not isinstance(df, pd.DataFrame):
|
|
19
|
+
raise TypeError("df must be a pandas DataFrame")
|
|
20
|
+
_safe_name(table)
|
|
21
|
+
if if_exists not in {"replace", "append", "fail"}:
|
|
22
|
+
raise ValueError("if_exists must be replace, append or fail")
|
|
23
|
+
with sqlite3.connect(str(database)) as conn:
|
|
24
|
+
count = df.to_sql(table, conn, if_exists=if_exists, index=False)
|
|
25
|
+
return int(count)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def sqlite_group_summary(database: str | Path, *, table="transactions",
|
|
29
|
+
group_by="wilaya", amount_column="montant_da") -> pd.DataFrame:
|
|
30
|
+
"""Return count/sum/mean, computed by SQLite using a safe fixed SQL pattern."""
|
|
31
|
+
t, g, a = _safe_name(table), _safe_name(group_by), _safe_name(amount_column)
|
|
32
|
+
sql = (f"SELECT {g} AS category, COUNT({a}) AS count, SUM({a}) AS total, "
|
|
33
|
+
f"AVG({a}) AS mean FROM {t} GROUP BY {g} ORDER BY total DESC")
|
|
34
|
+
with sqlite3.connect(str(database)) as conn:
|
|
35
|
+
return pd.read_sql_query(sql, conn)
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""Introductory inferential statistics. Each test explicitly reports H0."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from scipy import stats
|
|
7
|
+
from ._results import TestResult, make_result
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _sample(x, minimum=2):
|
|
11
|
+
s = pd.to_numeric(pd.Series(x), errors="raise").dropna().to_numpy(dtype=float)
|
|
12
|
+
if s.size < minimum or not np.all(np.isfinite(s)):
|
|
13
|
+
raise ValueError(f"Need at least {minimum} finite numeric observations")
|
|
14
|
+
return s
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def shapiro_test(x, alpha=0.05) -> TestResult:
|
|
18
|
+
a = _sample(x, 3)
|
|
19
|
+
if np.ptp(a) == 0:
|
|
20
|
+
raise ValueError("Shapiro-Wilk is undefined for constant data")
|
|
21
|
+
result = stats.shapiro(a)
|
|
22
|
+
note = "قد تكون p-value غير دقيقة للعينات أكبر من 5000." if len(a) > 5000 else ""
|
|
23
|
+
return make_result("Shapiro-Wilk", result.statistic, result.pvalue, len(a), alpha,
|
|
24
|
+
"تتبع البيانات توزيعًا طبيعيًا.", note)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def jarque_bera_test(x, alpha=0.05) -> TestResult:
|
|
28
|
+
a = _sample(x, 3)
|
|
29
|
+
if np.ptp(a) == 0:
|
|
30
|
+
raise ValueError("Jarque-Bera is undefined for constant data")
|
|
31
|
+
r = stats.jarque_bera(a)
|
|
32
|
+
return make_result("Jarque-Bera", r.statistic, r.pvalue, len(a), alpha,
|
|
33
|
+
"تتفق الالتواء والتفرطح مع التوزيع الطبيعي.")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def normality_test(x, method="shapiro", alpha=0.05) -> TestResult:
|
|
37
|
+
"""method='shapiro' or 'jarque_bera'."""
|
|
38
|
+
if method == "shapiro":
|
|
39
|
+
return shapiro_test(x, alpha)
|
|
40
|
+
if method == "jarque_bera":
|
|
41
|
+
return jarque_bera_test(x, alpha)
|
|
42
|
+
raise ValueError("method must be 'shapiro' or 'jarque_bera'")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def independent_t_test(a, b, alpha=0.05, equal_var=False) -> TestResult:
|
|
46
|
+
x, y = _sample(a), _sample(b)
|
|
47
|
+
r = stats.ttest_ind(x, y, equal_var=equal_var)
|
|
48
|
+
name = "Student t-test" if equal_var else "Welch t-test"
|
|
49
|
+
return make_result(name, r.statistic, r.pvalue, len(x)+len(y), alpha,
|
|
50
|
+
"متوسطا المجموعتين متساويان.")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def paired_t_test(a, b, alpha=0.05) -> TestResult:
|
|
54
|
+
x, y = np.asarray(a, dtype=float), np.asarray(b, dtype=float)
|
|
55
|
+
if x.shape != y.shape:
|
|
56
|
+
raise ValueError("Paired samples must have the same shape")
|
|
57
|
+
good = np.isfinite(x) & np.isfinite(y)
|
|
58
|
+
x, y = x[good], y[good]
|
|
59
|
+
if len(x) < 2:
|
|
60
|
+
raise ValueError("Need at least two valid pairs")
|
|
61
|
+
r = stats.ttest_rel(x, y)
|
|
62
|
+
return make_result("Paired t-test", r.statistic, r.pvalue, len(x), alpha,
|
|
63
|
+
"متوسط الفروق بين القياسين يساوي صفرًا.")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def mann_whitney_test(a, b, alpha=0.05) -> TestResult:
|
|
67
|
+
x, y = _sample(a), _sample(b)
|
|
68
|
+
r = stats.mannwhitneyu(x, y, alternative="two-sided")
|
|
69
|
+
return make_result("Mann-Whitney U", r.statistic, r.pvalue, len(x)+len(y), alpha,
|
|
70
|
+
"توزيعا المجموعتين متماثلان.",
|
|
71
|
+
"لا يُفسَّر تلقائيًا على أنه اختبار تساوي الوسيطين.")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def wilcoxon_test(a, b, alpha=0.05) -> TestResult:
|
|
75
|
+
x, y = np.asarray(a, dtype=float), np.asarray(b, dtype=float)
|
|
76
|
+
if x.shape != y.shape:
|
|
77
|
+
raise ValueError("Paired samples must have the same shape")
|
|
78
|
+
valid = np.isfinite(x) & np.isfinite(y)
|
|
79
|
+
x, y = x[valid], y[valid]
|
|
80
|
+
if len(x) < 2 or np.allclose(x-y, 0):
|
|
81
|
+
raise ValueError("Need at least two pairs and a nonzero difference")
|
|
82
|
+
r = stats.wilcoxon(x, y)
|
|
83
|
+
return make_result("Wilcoxon signed-rank", r.statistic, r.pvalue, len(x), alpha,
|
|
84
|
+
"توزيع الفروق متناظر حول الصفر.")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def anova_test(*groups, alpha=0.05) -> TestResult:
|
|
88
|
+
if len(groups) < 2:
|
|
89
|
+
raise ValueError("Provide at least two groups")
|
|
90
|
+
arrays = [_sample(group) for group in groups]
|
|
91
|
+
r = stats.f_oneway(*arrays)
|
|
92
|
+
return make_result("One-way ANOVA", r.statistic, r.pvalue,
|
|
93
|
+
sum(map(len, arrays)), alpha, "متوسطات كل المجموعات متساوية.")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def kruskal_test(*groups, alpha=0.05) -> TestResult:
|
|
97
|
+
if len(groups) < 2:
|
|
98
|
+
raise ValueError("Provide at least two groups")
|
|
99
|
+
arrays = [_sample(group) for group in groups]
|
|
100
|
+
r = stats.kruskal(*arrays)
|
|
101
|
+
return make_result("Kruskal-Wallis", r.statistic, r.pvalue,
|
|
102
|
+
sum(map(len, arrays)), alpha, "توزيعات المجموعات متماثلة.")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def levene_test(*groups, alpha=0.05) -> TestResult:
|
|
106
|
+
if len(groups) < 2:
|
|
107
|
+
raise ValueError("Provide at least two groups")
|
|
108
|
+
arrays = [_sample(group) for group in groups]
|
|
109
|
+
r = stats.levene(*arrays, center="median")
|
|
110
|
+
return make_result("Brown-Forsythe (Levene)", r.statistic, r.pvalue,
|
|
111
|
+
sum(map(len, arrays)), alpha, "تباينات المجموعات متساوية.")
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def chi_square_test(contingency_table, alpha=0.05) -> TestResult:
|
|
115
|
+
table = np.asarray(contingency_table, dtype=float)
|
|
116
|
+
if table.ndim != 2 or min(table.shape) < 2 or not np.all(np.isfinite(table)):
|
|
117
|
+
raise ValueError("Expected a finite 2D contingency table >= 2x2")
|
|
118
|
+
if np.any(table < 0) or np.any(table.sum(axis=0) == 0) or np.any(table.sum(axis=1) == 0):
|
|
119
|
+
raise ValueError("Table needs nonnegative counts and nonzero margins")
|
|
120
|
+
r = stats.chi2_contingency(table, correction=False)
|
|
121
|
+
notes = "راجع التكرارات المتوقعة؛ قد يلزم اختبار دقيق عند ندرتها."
|
|
122
|
+
return make_result("Pearson Chi-square independence", r.statistic, r.pvalue,
|
|
123
|
+
int(table.sum()), alpha, "المتغيران الفئويان مستقلان.", notes)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def pearson_test(a, b, alpha=0.05) -> TestResult:
|
|
127
|
+
x, y = _paired(a, b)
|
|
128
|
+
r = stats.pearsonr(x, y)
|
|
129
|
+
return make_result("Pearson correlation", r.statistic, r.pvalue,
|
|
130
|
+
len(x), alpha, "الارتباط الخطي السكاني يساوي صفرًا.")
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def spearman_test(a, b, alpha=0.05) -> TestResult:
|
|
134
|
+
x, y = _paired(a, b)
|
|
135
|
+
r = stats.spearmanr(x, y)
|
|
136
|
+
return make_result("Spearman correlation", r.statistic, r.pvalue,
|
|
137
|
+
len(x), alpha, "الارتباط الرتبي السكاني يساوي صفرًا.")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _paired(a, b):
|
|
141
|
+
x, y = np.asarray(a, dtype=float), np.asarray(b, dtype=float)
|
|
142
|
+
if x.shape != y.shape or x.ndim != 1:
|
|
143
|
+
raise ValueError("Need aligned one-dimensional samples")
|
|
144
|
+
valid = np.isfinite(x) & np.isfinite(y)
|
|
145
|
+
x, y = x[valid], y[valid]
|
|
146
|
+
if len(x) < 3 or np.std(x) == 0 or np.std(y) == 0:
|
|
147
|
+
raise ValueError("Need at least 3 variable paired observations")
|
|
148
|
+
return x, y
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def ks_two_sample_test(a, b, alpha=0.05) -> TestResult:
|
|
152
|
+
x, y = _sample(a), _sample(b)
|
|
153
|
+
r = stats.ks_2samp(x, y)
|
|
154
|
+
return make_result("Two-sample Kolmogorov-Smirnov", r.statistic, r.pvalue,
|
|
155
|
+
len(x)+len(y), alpha, "المجموعتان تتبعان التوزيع نفسه.")
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def descriptive_summary(data: pd.DataFrame) -> pd.DataFrame:
|
|
159
|
+
"""Descriptive statistics for numeric columns only."""
|
|
160
|
+
if not isinstance(data, pd.DataFrame):
|
|
161
|
+
raise TypeError("data must be a pandas DataFrame")
|
|
162
|
+
return data.describe(include="number").T
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Time-safe lagged features for classroom forecasting exercises."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
import pandas as pd
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def lagged_features(series, *, lags=(1, 2, 3), rolling_window=None) -> pd.DataFrame:
|
|
7
|
+
"""Create past-only features: lag t-n and optional mean up to t-1.
|
|
8
|
+
|
|
9
|
+
Input must already be sorted in ascending time order. No random splitting
|
|
10
|
+
should be used to validate temporal forecasts.
|
|
11
|
+
"""
|
|
12
|
+
values = pd.Series(series)
|
|
13
|
+
if len(lags) < 1 or any(not isinstance(lag, int) or lag < 1 for lag in lags):
|
|
14
|
+
raise ValueError("lags must be positive integers")
|
|
15
|
+
out = pd.DataFrame(index=values.index)
|
|
16
|
+
for lag in lags:
|
|
17
|
+
out[f"lag_{lag}"] = values.shift(lag)
|
|
18
|
+
if rolling_window is not None:
|
|
19
|
+
if not isinstance(rolling_window, int) or rolling_window < 2:
|
|
20
|
+
raise ValueError("rolling_window must be an integer >= 2")
|
|
21
|
+
out[f"rolling_mean_{rolling_window}"] = values.shift(1).rolling(rolling_window).mean()
|
|
22
|
+
return out
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: khaled-dsml
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Educational data science, machine learning and big-data analysis toolkit with Arabic interpretations
|
|
5
|
+
Author-email: Khaled Ali <ali-khaled@univ-eloued.dz>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/noble2013/KhaledDSML
|
|
8
|
+
Project-URL: Repository, https://github.com/noble2013/KhaledDSML
|
|
9
|
+
Project-URL: Issues, https://github.com/noble2013/KhaledDSML/issues
|
|
10
|
+
Keywords: data-science,machine-learning,big-data,education,statistics,arabic
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Education
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: numpy>=1.26
|
|
24
|
+
Requires-Dist: pandas>=2.1
|
|
25
|
+
Requires-Dist: scipy>=1.11
|
|
26
|
+
Requires-Dist: scikit-learn>=1.3
|
|
27
|
+
Provides-Extra: econometrics
|
|
28
|
+
Requires-Dist: statsmodels>=0.14; extra == "econometrics"
|
|
29
|
+
Provides-Extra: dask
|
|
30
|
+
Requires-Dist: dask[dataframe]>=2024.1; extra == "dask"
|
|
31
|
+
Provides-Extra: spark
|
|
32
|
+
Requires-Dist: pyspark>=3.5; extra == "spark"
|
|
33
|
+
Provides-Extra: plots
|
|
34
|
+
Requires-Dist: matplotlib>=3.8; extra == "plots"
|
|
35
|
+
Provides-Extra: notebooks
|
|
36
|
+
Requires-Dist: jupyterlab>=4; extra == "notebooks"
|
|
37
|
+
Requires-Dist: ipykernel>=6; extra == "notebooks"
|
|
38
|
+
Requires-Dist: openpyxl>=3.1; extra == "notebooks"
|
|
39
|
+
Provides-Extra: dev
|
|
40
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
41
|
+
Requires-Dist: build>=1; extra == "dev"
|
|
42
|
+
Requires-Dist: twine>=5; extra == "dev"
|
|
43
|
+
Provides-Extra: all
|
|
44
|
+
Requires-Dist: statsmodels>=0.14; extra == "all"
|
|
45
|
+
Requires-Dist: matplotlib>=3.8; extra == "all"
|
|
46
|
+
Requires-Dist: jupyterlab>=4; extra == "all"
|
|
47
|
+
Requires-Dist: ipykernel>=6; extra == "all"
|
|
48
|
+
Requires-Dist: openpyxl>=3.1; extra == "all"
|
|
49
|
+
Dynamic: license-file
|
|
50
|
+
|
|
51
|
+
# KhaledDSML — علم البيانات وتعلّم الآلة وتحليل البيانات الضخمة
|
|
52
|
+
|
|
53
|
+
**الإعداد والتطوير: الأستاذ الدكتور خالد علي (Khaled Ali)**
|
|
54
|
+
**الجامعة:** جامعة الشهيد حمه لخضر – الوادي، الجزائر (University of El Oued, Algeria)
|
|
55
|
+
**الكلية:** كلية العلوم الاقتصادية والتجارية وعلوم التسيير (Faculty of Economic, Commercial and Management Sciences)
|
|
56
|
+
**البريد الإلكتروني المهني:** [ali-khaled@univ-eloued.dz](mailto:ali-khaled@univ-eloued.dz)
|
|
57
|
+
**الإصدار التجريبي: 0.1.0 — أكتوبر 2026**
|
|
58
|
+
|
|
59
|
+
حزمة Python تعليمية للمقاييس التالية:
|
|
60
|
+
|
|
61
|
+
- علم البيانات وتعلّم الآلة — السنة الثالثة ليسانس اقتصاد وتسيير المؤسسات.
|
|
62
|
+
- تحليل البيانات الضخمة — السنة الثانية ماستر اقتصاد كمي.
|
|
63
|
+
|
|
64
|
+
> تنبيه: تم تصميم المشروع وفق البرامج البيداغوجية التي أعدّها المؤلف؛ لا يُدَّعى تطابقه مع بطاقة وزارية رسمية دون مراجعتها. البيانات الاصطناعية للأغراض التعليمية فقط. الإحصاء الوصفي لا يثبت السببية، وعدم رفض الفرضية الصفرية لا يثبت صحتها.
|
|
65
|
+
|
|
66
|
+
## لماذا هذه المكتبة؟
|
|
67
|
+
|
|
68
|
+
- اختبارات فرضيات شائعة مع `statistic`، و`p_value`، وفرضية صفرية، وتفسير عربي.
|
|
69
|
+
- فحص جودة البيانات واكتشاف القيم الشاذة وتحسين الذاكرة.
|
|
70
|
+
- أنابيب تدريب آمنة من تسرب البيانات، وتقييم الانحدار والتصنيف والتجميع.
|
|
71
|
+
- معالجة CSV بالأجزاء، وتجميع شهري وجغرافي، وتوليد مليون معاملة اصطناعية إذا طُلب ذلك.
|
|
72
|
+
- 24 سؤال اختيار متعدد للتقويم التكويني مع التصحيح والتفسير.
|
|
73
|
+
- دعم Windows وJupyterLab وGoogle Colab بعد تثبيت الاعتمادات.
|
|
74
|
+
|
|
75
|
+
## التثبيت من المجلد المصدر — Windows PowerShell
|
|
76
|
+
|
|
77
|
+
افتح PowerShell من داخل المجلد الذي يحتوي `pyproject.toml`:
|
|
78
|
+
|
|
79
|
+
```powershell
|
|
80
|
+
py -3.13 -m venv .venv
|
|
81
|
+
.\.venv\Scripts\python.exe -m pip install --upgrade pip
|
|
82
|
+
.\.venv\Scripts\python.exe -m pip install -e ".[all,dev]"
|
|
83
|
+
.\.venv\Scripts\python.exe -m pytest
|
|
84
|
+
.\.venv\Scripts\khaled-dsml.exe demo --rows 1000
|
|
85
|
+
.\.venv\Scripts\khaled-dsml.exe quiz --course big_data --count 5
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
بعض بيئات Windows تستعمل `py` بدل `py -3.13` عند عدم توفر الفرع 3.13.
|
|
89
|
+
|
|
90
|
+
*لاحقًا وبعد النشر الفعلي على PyPI فقط:*
|
|
91
|
+
|
|
92
|
+
```powershell
|
|
93
|
+
py -m pip install khaled-dsml
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## أمثلة عملية
|
|
97
|
+
|
|
98
|
+
### اختبار اعتدالية
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from khaled_dsml import normality_test
|
|
102
|
+
result = normality_test([12, 13, 15, 17, 16, 14, 13, 18])
|
|
103
|
+
print(result.interpretation_ar)
|
|
104
|
+
print(result.p_value)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### كشف مشاكل البيانات
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from khaled_dsml import generate_transactions, quality_report, missingness_report
|
|
111
|
+
transactions = generate_transactions(100_000)
|
|
112
|
+
print(quality_report(transactions))
|
|
113
|
+
print(missingness_report(transactions))
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### تعلم الآلة (منع تسرب البيانات)
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
from sklearn.datasets import make_regression
|
|
120
|
+
from khaled_dsml import split_data, make_regressor, regression_metrics
|
|
121
|
+
X, y = make_regression(n_samples=200, n_features=3, noise=3, random_state=42)
|
|
122
|
+
X_train, X_test, y_train, y_test = split_data(X, y)
|
|
123
|
+
model = make_regressor(X_train, "ridge", alpha=1.0)
|
|
124
|
+
model.fit(X_train, y_train)
|
|
125
|
+
print(regression_metrics(y_test, model.predict(X_test)))
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### مليون معاملة والتجميع بالأجزاء
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
from khaled_dsml import generate_transactions, csv_chunk_summary, csv_chunk_monthly
|
|
132
|
+
transactions = generate_transactions(1_000_000)
|
|
133
|
+
transactions.to_csv("transactions.csv", index=False)
|
|
134
|
+
print(csv_chunk_summary("transactions.csv", group_by="wilaya"))
|
|
135
|
+
print(csv_chunk_monthly("transactions.csv"))
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
`read_csv(chunksize=...)` لا يحتاج تحميل الملف بكامله. **لكن** توليد مليون صف في `generate_transactions` يحتفظ بها في الذاكرة قبل الحفظ؛ اختر عدد صفوف أصغر لجهاز بذاكرة محدودة.
|
|
139
|
+
|
|
140
|
+
### أسئلة تصحيح آلي
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
from khaled_dsml import get_questions, grade_quiz
|
|
144
|
+
questions = get_questions("machine_learning", limit=3, seed=3)
|
|
145
|
+
for question in questions:
|
|
146
|
+
print(question.id, question.question)
|
|
147
|
+
answers = {q.id: "A" for q in questions} # إجابات افتراضية
|
|
148
|
+
print(grade_quiz(questions, answers))
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## واجهات المكتبة
|
|
152
|
+
|
|
153
|
+
| الوحدة | أمثلة الدوال |
|
|
154
|
+
|---|---|
|
|
155
|
+
| `statistics` | `normality_test`, `independent_t_test`, `paired_t_test`, `anova_test`, `kruskal_test`, `chi_square_test`, `pearson_test`, `spearman_test`, `levene_test`, `ks_two_sample_test` |
|
|
156
|
+
| `quality` | `quality_report`, `missingness_report`, `iqr_outliers`, `fill_missing_numeric`, `optimize_memory`, `distribution_drift_test` |
|
|
157
|
+
| `ml` | `make_regressor`, `make_classifier`, `cross_validation_summary`, `classification_metrics`, `regression_metrics`, `cluster_data`, `pca_summary` |
|
|
158
|
+
| `bigdata` | `generate_transactions`, `csv_chunk_summary`, `csv_chunk_missing`, `csv_chunk_monthly`, `map_reduce_word_count`, `json_documents_to_table` |
|
|
159
|
+
| `sqltools` | `sqlite_import_frame`, `sqlite_group_summary` (SQLite محلي لتجارب SQL) |
|
|
160
|
+
| `timeseries` | `lagged_features` (خصائص مؤخرة دون تسرب للمستقبل) |
|
|
161
|
+
| `distributed` | `dask_csv_summary`, `spark_group_summary` (تثبيت `[dask]` أو `[spark]` واستخدام محرك فعلي) |
|
|
162
|
+
| `econometrics` | `adf_test`, `vif_table`, `breusch_pagan_test`, `ljung_box_test`, `cronbach_alpha` (تثبيت `econometrics`) |
|
|
163
|
+
| `plots` | `plot_missingness`, `plot_monthly`, `plot_confusion` (تثبيت `plots`) |
|
|
164
|
+
| `quizzes` | `get_questions`, `grade_quiz` |
|
|
165
|
+
|
|
166
|
+
لخريطة المقياسين والتغطية التفصيلية، راجع [docs/COURSE_MAPPING_AR.md](docs/COURSE_MAPPING_AR.md). للحصول على أمثلة مكتملة راجع `examples/`.
|
|
167
|
+
|
|
168
|
+
**لإنشاء المستودع ونشر الحزمة بأمان عبر GitHub Actions وTrusted Publishing (بدون حفظ مفتاح API)، راجع [دليل النشر العربي](docs/PUBLISH_AR.md).**
|
|
169
|
+
|
|
170
|
+
## نشر الحزمة على TestPyPI وPyPI باستخدام GitHub Actions
|
|
171
|
+
|
|
172
|
+
هذه المكتبة **غير منشورة بعد** على PyPI أو TestPyPI؛ لا تستخدم أمر `pip install khaled-dsml` من PyPI إلى حين نشرها.
|
|
173
|
+
|
|
174
|
+
1. تابع نتائج الاختبارات والبناء في [GitHub Actions](https://github.com/noble2013/KhaledDSML/actions).
|
|
175
|
+
2. أنشئ حسابًا منفصلًا على [TestPyPI](https://test.pypi.org/account/register/) وبيئة `testpypi` في `Settings → Environments` بمستودع GitHub.
|
|
176
|
+
3. أضف في [TestPyPI Trusted Publishing](https://test.pypi.org/manage/account/publishing/) ناشرًا معلقًا للاسم `khaled-dsml` ومعلومات المستودع `noble2013/KhaledDSML`، وملف `testpypi.yml`، والبيئة `testpypi`.
|
|
177
|
+
4. بعد نجاح البناء، نفذ `Actions → Publish to TestPyPI (manual) → Run workflow`.
|
|
178
|
+
5. بعد نجاح التجربة، أنشئ البيئة `pypi` وناشرًا موثوقًا للاسم نفسه على [PyPI](https://pypi.org/manage/account/publishing/) باستخدام `release.yml` والبيئة `pypi`، ثم انشر GitHub Release يحمل الوسم `v0.1.0`.
|
|
179
|
+
|
|
180
|
+
يوصى بحماية بيئة `pypi` بمراجعة يدوية. لا تضع كلمات مرور أو رموز API داخل GitHub. اقرأ [دليل النشر العربي](docs/PUBLISH_AR.md) للمزيد.
|
|
181
|
+
|
|
182
|
+
## Notes (English)
|
|
183
|
+
|
|
184
|
+
KhaledDSML is an **educational** Python toolkit; it wraps proven NumPy, pandas, SciPy and scikit-learn primitives rather than reinventing statistical algorithms. Optional statistics are backed by statsmodels. It is not a distributed processing engine: the MapReduce example runs locally; Optional Dask and Spark adapters are included, but need real engines and are not validated on a Spark cluster in version 0.1.0. Do not fit preprocessing on test data. Hypothesis-test interpretations should be checked against sampling design and assumptions. An MIT license covers this original wrapper code, not a claim of authorship over third-party algorithms.
|
|
185
|
+
|
|
186
|
+
## حقوق الاستخدام
|
|
187
|
+
|
|
188
|
+
الترخيص MIT. الاسم المقترح للنشر هو `khaled-dsml` (يلزم التحقق من توفره عند رفع الحزمة). كل الإضافات يمكن مراجعتها عبر اختبارات `pytest` قبل النشر. حقوق المنهجيات والخوارزميات الأساسية محفوظة لمؤلفيها ومشروعاتها الأصلية.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
khaled_dsml/__init__.py,sha256=iRLVRN4MK57sdy9y2gEktx_p-TZAUL3-BxU_lbfKGk8,1026
|
|
2
|
+
khaled_dsml/_results.py,sha256=ZIMEhQwUu7OxfLBp7FG9-IDinQOVoc23ys3QT8X9RnA,1357
|
|
3
|
+
khaled_dsml/bigdata.py,sha256=8i1wUjgtRk3vZJu7B-MuBOzMVVTG5ifRCdHSQ7Tp3TE,4982
|
|
4
|
+
khaled_dsml/cli.py,sha256=kXLfODk_fozZc2nSdDSusGpYHUIGZeXcxPXhSM49cNk,1557
|
|
5
|
+
khaled_dsml/distributed.py,sha256=3yoe84VQUOyRJYR4i0HajcOn7t9PbG3zDkLSzlQthp8,1565
|
|
6
|
+
khaled_dsml/econometrics.py,sha256=nMFLh2sC3K6xHbZPdSDISo1fbVSCugDlAIm3H4JqseE,3753
|
|
7
|
+
khaled_dsml/ml.py,sha256=_9lPEl9K37vHZWMyi64MUdEaNwxeRtpgNdbUNZuQhGE,7144
|
|
8
|
+
khaled_dsml/plots.py,sha256=o3Tvc-logeaZNtndQIVd8t4M84lbuH2pLSU33omexUE,1118
|
|
9
|
+
khaled_dsml/quality.py,sha256=iO-7UH0aPdQD0tv91HXxjquNrLYlALRt5i1t13Jho6o,4513
|
|
10
|
+
khaled_dsml/quizzes.py,sha256=6qUjsu7WuHHoPFSdFBcOdx_3BSRFUAM03-Q1RbmkCUM,8578
|
|
11
|
+
khaled_dsml/sqltools.py,sha256=DMgh7iq_Q22BIyHMi6ZPHSFjVV3H9aOXzNdS22evOQg,1580
|
|
12
|
+
khaled_dsml/statistics.py,sha256=jjMlLFw-br97xv762Q0gGb3LZGe4cNIVQdW1mdbtwiA,7200
|
|
13
|
+
khaled_dsml/timeseries.py,sha256=CNl0xmPf8hUCafXAx4qJZnT_JKraRidjdoZ57GLwnM0,988
|
|
14
|
+
khaled_dsml-0.1.0.dist-info/licenses/LICENSE,sha256=f7as3Fpf2T2Z9GAKUuCt7dzd2fdQCh8FldlmMGs3Hx4,1067
|
|
15
|
+
khaled_dsml-0.1.0.dist-info/METADATA,sha256=GnWw6sPdwAzA8qHahFv6K3IShbTe10Y8GTJbZqqDrf4,11102
|
|
16
|
+
khaled_dsml-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
17
|
+
khaled_dsml-0.1.0.dist-info/entry_points.txt,sha256=igTVGou4rpnn0Ik3oZKnB4REyvSzNh1QelHldW5Mqkk,53
|
|
18
|
+
khaled_dsml-0.1.0.dist-info/top_level.txt,sha256=d9Gk3AokLxRE8eFeC417BYZBC3JmSBxWF1denSkt5fA,12
|
|
19
|
+
khaled_dsml-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Khaled Ali
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
khaled_dsml
|