Εισαγωγή
Η στατιστική ανάλυση είναι, ουσιαστικά, η ραχοκοκαλιά κάθε σοβαρής δουλειάς στην επιστήμη δεδομένων. Είτε θέλετε να ελέγξετε μια επιχειρηματική υπόθεση, είτε να χτίσετε ένα προγνωστικό μοντέλο, είτε να βγάλετε συμπεράσματα από πειραματικά δεδομένα — η στατιστική σάς δίνει το μαθηματικό πλαίσιο που μετατρέπει ακατέργαστα νούμερα σε αξιόπιστη γνώση.
Και η Python; Χάρη στο πλούσιο οικοσύστημα βιβλιοθηκών της, έχει καθιερωθεί ως η κορυφαία γλώσσα για στατιστική ανάλυση. Δύο βιβλιοθήκες ξεχωρίζουν ιδιαίτερα: η SciPy και η statsmodels.
Η SciPy (έκδοση 1.17, Ιανουάριος 2026) προσφέρει ένα τεράστιο φάσμα στατιστικών συναρτήσεων, κατανομών πιθανότητας και ελέγχων υποθέσεων μέσα από το module scipy.stats. Η statsmodels (έκδοση 0.14.6, Δεκέμβριος 2025) εξειδικεύεται στην οικονομετρία και τη στατιστική μοντελοποίηση, με λεπτομερείς πίνακες αποτελεσμάτων που θυμίζουν πολύ αυτούς της R. Μαζί, καλύπτουν σχεδόν κάθε ανάγκη στατιστικής ανάλυσης που θα συναντήσετε.
Λοιπόν, ας μπούμε στο ψητό. Σε αυτόν τον οδηγό θα δούμε πρακτικά παραδείγματα περιγραφικής στατιστικής, κατανομών πιθανότητας, ελέγχων υποθέσεων, γραμμικής παλινδρόμησης και ανάλυσης συσχέτισης. Αν έχετε ήδη περάσει από τους προηγούμενους οδηγούς μας για Pandas, καθαρισμό δεδομένων και οπτικοποίηση με Matplotlib, είστε έτοιμοι για το επόμενο βήμα.
Εγκατάσταση και Ρύθμιση Περιβάλλοντος
Πριν ξεκινήσουμε, χρειάζεται να εγκαταστήσουμε τις απαραίτητες βιβλιοθήκες. Μια σημαντική λεπτομέρεια: η SciPy 1.17 απαιτεί Python 3.11 ή νεότερη έκδοση, οπότε βεβαιωθείτε ότι το περιβάλλον σας είναι ενημερωμένο.
pip install scipy==1.17.0 statsmodels==0.14.6 numpy pandas matplotlib
Ελέγξτε ότι όλα εγκαταστάθηκαν σωστά:
import scipy
import statsmodels
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
print(f"SciPy: {scipy.__version__}")
print(f"Statsmodels: {statsmodels.__version__}")
print(f"NumPy: {np.__version__}")
print(f"Pandas: {pd.__version__}")
Αν δεν εμφανιστούν σφάλματα, είστε έτοιμοι. Για τα παραδείγματα που ακολουθούν, θα χρησιμοποιούμε τα εξής imports ως βάση:
import numpy as np
import pandas as pd
from scipy import stats
import statsmodels.api as sm
import statsmodels.formula.api as smf
import matplotlib.pyplot as plt
Περιγραφική Στατιστική με Pandas και NumPy
Η περιγραφική στατιστική μάς βοηθά να κατανοήσουμε τη δομή και τα βασικά χαρακτηριστικά ενός συνόλου δεδομένων πριν προχωρήσουμε σε πιο σύνθετες αναλύσεις. Είναι, αν θέλετε, το πρώτο βήμα σε κάθε data analysis pipeline.
Τα βασικά μέτρα περιλαμβάνουν τη μέση τιμή (mean), τη διάμεσο (median), την επικρατούσα τιμή (mode), την τυπική απόκλιση (standard deviation), τη διασπορά (variance) και τα ποσοστημόρια (percentiles).
Ας δημιουργήσουμε ένα πρακτικό παράδειγμα με βαθμολογίες φοιτητών:
# Δημιουργία δείγματος δεδομένων - βαθμολογίες φοιτητών
np.random.seed(42)
data = {
'Όνομα': [f'Φοιτητής_{i}' for i in range(1, 51)],
'Μαθηματικά': np.random.normal(loc=7.0, scale=1.5, size=50).clip(0, 10).round(1),
'Φυσική': np.random.normal(loc=6.5, scale=2.0, size=50).clip(0, 10).round(1),
'Πληροφορική': np.random.normal(loc=7.5, scale=1.2, size=50).clip(0, 10).round(1),
}
df = pd.DataFrame(data)
# Γενική επισκόπηση με describe()
print(df[['Μαθηματικά', 'Φυσική', 'Πληροφορική']].describe())
# Επιμέρους μέτρα
print(f"\nΜέση τιμή Μαθηματικών: {np.mean(df['Μαθηματικά']):.2f}")
print(f"Διάμεσος Μαθηματικών: {np.median(df['Μαθηματικά']):.2f}")
print(f"Τυπική απόκλιση: {np.std(df['Μαθηματικά'], ddof=1):.2f}")
print(f"Διασπορά: {np.var(df['Μαθηματικά'], ddof=1):.2f}")
# Επικρατούσα τιμή με scipy.stats.mode
mode_result = stats.mode(df['Μαθηματικά'], keepdims=True)
print(f"Επικρατούσα τιμή: {mode_result.mode[0]}")
# Ποσοστημόρια
percentiles = np.percentile(df['Μαθηματικά'], [25, 50, 75])
print(f"25ο, 50ο, 75ο εκατοστημόριο: {percentiles}")
Η μέθοδος describe() του Pandas σάς δίνει μια γρήγορη σύνοψη — count, mean, std, min, max και τα τρία βασικά ποσοστημόρια, όλα μαζί. Για πιο εξειδικευμένους υπολογισμούς όμως, οι συναρτήσεις του NumPy και της SciPy δίνουν πλήρη έλεγχο.
Σημειώστε τη χρήση του ddof=1 για τον υπολογισμό της δειγματικής τυπικής απόκλισης (διαίρεση με N-1 αντί N). Είναι μια λεπτομέρεια που πολλοί αρχάριοι παραβλέπουν, αλλά κάνει διαφορά στην ακρίβεια των αποτελεσμάτων.
Κατανομές Πιθανότητας
Η κατανόηση των κατανομών πιθανότητας είναι θεμελιώδης για τη στατιστική ανάλυση. Ειλικρινά, χωρίς αυτή τη βάση, πολλά από τα υπόλοιπα δεν βγάζουν νόημα.
Η scipy.stats περιλαμβάνει πάνω από 100 κατανομές — ας δούμε τις τρεις πιο σημαντικές: την κανονική κατανομή, την κατανομή t και την κατανομή χ².
# --- Κανονική Κατανομή ---
mu, sigma = 70, 10 # μέση τιμή και τυπική απόκλιση
x = np.linspace(30, 110, 200)
# Συνάρτηση πυκνότητας πιθανότητας (PDF)
pdf_values = stats.norm.pdf(x, loc=mu, scale=sigma)
# Αθροιστική συνάρτηση κατανομής (CDF)
cdf_values = stats.norm.cdf(x, loc=mu, scale=sigma)
# Πιθανότητα βαθμολογίας μεταξύ 60 και 80
prob = stats.norm.cdf(80, loc=mu, scale=sigma) - stats.norm.cdf(60, loc=mu, scale=sigma)
print(f"P(60 ≤ X ≤ 80) = {prob:.4f}")
# Δημιουργία τυχαίων δεδομένων
random_data = stats.norm.rvs(loc=mu, scale=sigma, size=1000, random_state=42)
# --- Κατανομή t (Student) ---
df_t = 10 # βαθμοί ελευθερίας
t_values = stats.t.pdf(x=np.linspace(-4, 4, 200), df=df_t)
# --- Κατανομή Χ² ---
df_chi = 5
chi_values = stats.chi2.pdf(x=np.linspace(0, 20, 200), df=df_chi)
# Οπτικοποίηση κανονικής κατανομής
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
axes[0].plot(x, pdf_values, 'b-', linewidth=2)
axes[0].fill_between(x, pdf_values, where=(x >= 60) & (x <= 80), alpha=0.3)
axes[0].set_title('Κανονική Κατανομή - PDF')
axes[0].set_xlabel('Τιμή')
axes[0].set_ylabel('Πυκνότητα')
axes[1].plot(x, cdf_values, 'r-', linewidth=2)
axes[1].set_title('Κανονική Κατανομή - CDF')
axes[1].set_xlabel('Τιμή')
axes[1].set_ylabel('Αθροιστική Πιθανότητα')
plt.tight_layout()
plt.savefig('distributions.png', dpi=150)
plt.show()
Ένα πράγμα που αξίζει να θυμάστε: κάθε κατανομή στη scipy.stats προσφέρει τις μεθόδους pdf(), cdf(), rvs(), ppf() και fit(). Αυτή η ομοιόμορφη διεπαφή είναι τεράστιο πλεονέκτημα — μαθαίνετε μία φορά πώς λειτουργεί και μετά απλά αλλάζετε κατανομή.
Έλεγχος Υποθέσεων
Εδώ μπαίνουμε στην καρδιά της επαγωγικής στατιστικής. Ο έλεγχος υποθέσεων μας επιτρέπει να εξετάσουμε αν τα αποτελέσματά μας είναι στατιστικά σημαντικά ή οφείλονται απλώς στην τύχη.
Η βασική διαδικασία είναι σχετικά απλή: ορίζουμε τη μηδενική υπόθεση (H₀) και την εναλλακτική (H₁), υπολογίζουμε ένα στατιστικό ελέγχου, βγάζουμε p-value, και αποφασίζουμε βάσει του επιπέδου σημαντικότητας (συνήθως α = 0.05). Στην πράξη, η Python κάνει τους υπολογισμούς — εμείς πρέπει να ξέρουμε τι σημαίνουν τα αποτελέσματα.
t-Test (Έλεγχος t)
Ο έλεγχος t χρησιμοποιείται για τη σύγκριση μέσων τιμών. Υπάρχουν τρεις παραλλαγές: ενός δείγματος (σύγκριση με γνωστή τιμή), δύο ανεξάρτητων δειγμάτων (σύγκριση δύο ομάδων) και ζευγαρωτός (σύγκριση πριν-μετά στο ίδιο δείγμα). Ας τους δούμε όλους:
np.random.seed(42)
# Βαθμολογίες δύο τμημάτων
class_a = np.random.normal(loc=7.2, scale=1.5, size=35).clip(0, 10)
class_b = np.random.normal(loc=6.5, scale=1.8, size=35).clip(0, 10)
# --- Έλεγχος t ενός δείγματος ---
# H₀: Ο μέσος όρος του Τμήματος Α είναι 7.0
t_stat, p_value = stats.ttest_1samp(class_a, popmean=7.0)
print("=== Έλεγχος t ενός δείγματος ===")
print(f"t-statistic: {t_stat:.4f}, p-value: {p_value:.4f}")
print(f"Συμπέρασμα: {'Απορρίπτουμε' if p_value < 0.05 else 'Δεν απορρίπτουμε'} τη H₀\n")
# --- Έλεγχος t δύο ανεξάρτητων δειγμάτων ---
# H₀: Οι μέσοι όροι των δύο τμημάτων είναι ίσοι
t_stat, p_value = stats.ttest_ind(class_a, class_b)
print("=== Έλεγχος t δύο δειγμάτων ===")
print(f"Μέσος Τμήμα Α: {class_a.mean():.2f}, Τμήμα Β: {class_b.mean():.2f}")
print(f"t-statistic: {t_stat:.4f}, p-value: {p_value:.4f}")
print(f"Συμπέρασμα: {'Απορρίπτουμε' if p_value < 0.05 else 'Δεν απορρίπτουμε'} τη H₀\n")
# Έκδοση Welch (δεν υποθέτει ίσες διασπορές)
t_stat_w, p_value_w = stats.ttest_ind(class_a, class_b, equal_var=False)
print(f"Welch t-test: t={t_stat_w:.4f}, p={p_value_w:.4f}\n")
# --- Ζευγαρωτός έλεγχος t ---
# Βαθμολογίες πριν και μετά από φροντιστήριο
before = np.random.normal(loc=6.0, scale=1.5, size=30).clip(0, 10)
after = before + np.random.normal(loc=0.8, scale=0.5, size=30)
after = after.clip(0, 10)
t_stat, p_value = stats.ttest_rel(before, after)
print("=== Ζευγαρωτός έλεγχος t ===")
print(f"Μέσος πριν: {before.mean():.2f}, μετά: {after.mean():.2f}")
print(f"t-statistic: {t_stat:.4f}, p-value: {p_value:.4f}")
print(f"Συμπέρασμα: {'Στατιστικά σημαντική βελτίωση' if p_value < 0.05 else 'Μη σημαντική διαφορά'}")
Μια μικρή σημείωση: στην πράξη, η έκδοση Welch (με equal_var=False) είναι σχεδόν πάντα η πιο ασφαλής επιλογή, γιατί δεν υποθέτει ίσες διασπορές μεταξύ των ομάδων. Πολλοί στατιστικολόγοι τη συνιστούν ως default.
ANOVA (Ανάλυση Διασποράς)
Η ANOVA (Analysis of Variance) χρησιμοποιείται όταν θέλουμε να συγκρίνουμε τους μέσους όρους τριών ή περισσότερων ομάδων ταυτόχρονα. Γιατί δεν κάνουμε απλά πολλαπλά t-tests; Γιατί αυτό αυξάνει δραματικά τον κίνδυνο σφάλματος τύπου I — δηλαδή να βρούμε «σημαντική» διαφορά που στην πραγματικότητα δεν υπάρχει.
np.random.seed(42)
# Τρεις μέθοδοι διδασκαλίας - βαθμολογίες μαθητών
method_traditional = np.random.normal(loc=6.5, scale=1.5, size=30).clip(0, 10)
method_interactive = np.random.normal(loc=7.5, scale=1.3, size=30).clip(0, 10)
method_online = np.random.normal(loc=6.8, scale=1.8, size=30).clip(0, 10)
# One-way ANOVA
f_stat, p_value = stats.f_oneway(method_traditional, method_interactive, method_online)
print("=== One-way ANOVA ===")
print(f"F-statistic: {f_stat:.4f}")
print(f"p-value: {p_value:.4f}")
print(f"Συμπέρασμα: {'Υπάρχει σημαντική διαφορά' if p_value < 0.05 else 'Δεν υπάρχει σημαντική διαφορά'} μεταξύ ομάδων\n")
# Post-hoc ανάλυση με Tukey HSD
from statsmodels.stats.multicomp import pairwise_tukeyhsd
# Προετοιμασία δεδομένων για Tukey
all_scores = np.concatenate([method_traditional, method_interactive, method_online])
all_groups = (['Παραδοσιακή'] * 30 + ['Διαδραστική'] * 30 + ['Διαδικτυακή'] * 30)
tukey_result = pairwise_tukeyhsd(all_scores, all_groups, alpha=0.05)
print("=== Post-hoc Tukey HSD ===")
print(tukey_result)
Η ανάλυση Tukey HSD μάς δείχνει ποια συγκεκριμένα ζεύγη ομάδων διαφέρουν σημαντικά. Η στήλη reject είναι αυτή που σας ενδιαφέρει περισσότερο — σας λέει αν απορρίπτουμε τη μηδενική υπόθεση ισότητας για κάθε ζεύγος.
Χ² Έλεγχος (Chi-Square Test)
Ο έλεγχος χ² χρησιμοποιείται για κατηγορικά δεδομένα. Ελέγχει αν δύο κατηγορικές μεταβλητές είναι ανεξάρτητες μεταξύ τους. Ας δούμε ένα παράδειγμα: υπάρχει σχέση μεταξύ φύλου και προτίμησης προϊόντος;
# Πίνακας συνάφειας: Φύλο x Προτίμηση Προϊόντος
observed = pd.DataFrame(
[[45, 30, 25],
[35, 40, 25]],
index=['Άνδρες', 'Γυναίκες'],
columns=['Προϊόν Α', 'Προϊόν Β', 'Προϊόν Γ']
)
print("Πίνακας Παρατηρούμενων Συχνοτήτων:")
print(observed)
# Έλεγχος χ² ανεξαρτησίας
chi2, p_value, dof, expected = stats.chi2_contingency(observed.values)
print(f"\nΧ² statistic: {chi2:.4f}")
print(f"p-value: {p_value:.4f}")
print(f"Βαθμοί ελευθερίας: {dof}")
print(f"\nΑναμενόμενες συχνότητες:")
print(pd.DataFrame(expected, index=observed.index, columns=observed.columns).round(2))
print(f"\nΣυμπέρασμα: {'Υπάρχει' if p_value < 0.05 else 'Δεν υπάρχει'} σημαντική σχέση "
f"μεταξύ φύλου και προτίμησης")
Παρακάτω θα βρείτε έναν συνοπτικό πίνακα με τους βασικούς στατιστικούς ελέγχους. Τον χρησιμοποιώ κι εγώ σαν cheatsheet — αξίζει να τον κρατήσετε:
| Στατιστικός Έλεγχος | Συνάρτηση SciPy/Statsmodels | Πότε Χρησιμοποιείται | Τύπος Δεδομένων |
|---|---|---|---|
| t-test ενός δείγματος | stats.ttest_1samp() |
Σύγκριση μέσου όρου με γνωστή τιμή | Συνεχή |
| t-test δύο δειγμάτων | stats.ttest_ind() |
Σύγκριση μέσων δύο ανεξάρτητων ομάδων | Συνεχή |
| Ζευγαρωτός t-test | stats.ttest_rel() |
Σύγκριση πριν-μετά στο ίδιο δείγμα | Συνεχή |
| One-way ANOVA | stats.f_oneway() |
Σύγκριση μέσων ≥3 ομάδων | Συνεχή |
| Tukey HSD | pairwise_tukeyhsd() |
Post-hoc σύγκριση ζευγών μετά από ANOVA | Συνεχή |
| Χ² ανεξαρτησίας | stats.chi2_contingency() |
Σχέση μεταξύ κατηγορικών μεταβλητών | Κατηγορικά |
| Mann-Whitney U | stats.mannwhitneyu() |
Μη παραμετρική εναλλακτική του t-test | Τακτικά/Συνεχή |
| Kruskal-Wallis | stats.kruskal() |
Μη παραμετρική εναλλακτική της ANOVA | Τακτικά/Συνεχή |
Γραμμική Παλινδρόμηση με Statsmodels
Η γραμμική παλινδρόμηση είναι ίσως το πιο θεμελιώδες εργαλείο στατιστικής μοντελοποίησης. Κι εδώ η statsmodels πραγματικά λάμπει — παρέχει αναλυτικές στατιστικές πληροφορίες που απλά δεν θα βρείτε στο scikit-learn.
Ας δημιουργήσουμε ένα μοντέλο πρόβλεψης μισθού βάσει ετών εμπειρίας:
np.random.seed(42)
# Δημιουργία δεδομένων: Μισθός βάσει ετών εμπειρίας
n = 100
years_experience = np.random.uniform(0, 20, n)
# Μισθός = 25000 + 3000 * χρόνια + θόρυβος
salary = 25000 + 3000 * years_experience + np.random.normal(0, 5000, n)
df_salary = pd.DataFrame({
'Εμπειρία': years_experience,
'Μισθός': salary
})
# --- Μέθοδος 1: Formula API (συνιστάται) ---
model = smf.ols('Μισθός ~ Εμπειρία', data=df_salary).fit()
print(model.summary())
# --- Μέθοδος 2: Κλασική OLS API ---
X = sm.add_constant(df_salary['Εμπειρία']) # Προσθήκη σταθερού όρου
y = df_salary['Μισθός']
model_classic = sm.OLS(y, X).fit()
Ο πίνακας summary() περιέχει πληθώρα πληροφοριών. Ας αναλύσουμε τα πιο σημαντικά:
- R² (R-squared): Το ποσοστό διασποράς που εξηγείται από το μοντέλο. Π.χ. τιμή 0.60 σημαίνει ότι η εμπειρία εξηγεί το 60% της μεταβλητότητας του μισθού.
- Adj. R²: Προσαρμοσμένο R² που λαμβάνει υπόψη τον αριθμό μεταβλητών. Πιο αξιόπιστο σε πολλαπλή παλινδρόμηση.
- Συντελεστές (coef): Ο Intercept είναι ο αρχικός μισθός, ενώ ο συντελεστής Εμπειρίας δείχνει πόσο αυξάνεται ο μισθός ανά έτος.
- p-value (P>|t|): Αν είναι μικρότερο από 0.05, ο συντελεστής θεωρείται στατιστικά σημαντικός.
- Διαστήματα εμπιστοσύνης [0.025, 0.975]: Το 95% CI για κάθε συντελεστή.
# Ερμηνεία αποτελεσμάτων
print(f"Σταθερός όρος (αρχικός μισθός): {model.params['Intercept']:.2f}€")
print(f"Αύξηση ανά έτος εμπειρίας: {model.params['Εμπειρία']:.2f}€")
print(f"R²: {model.rsquared:.4f}")
print(f"p-value εμπειρίας: {model.pvalues['Εμπειρία']:.6f}")
# Διαστήματα εμπιστοσύνης συντελεστών
print(f"\nΔιαστήματα εμπιστοσύνης 95%:")
print(model.conf_int())
# Πρόβλεψη με διάστημα εμπιστοσύνης
new_data = pd.DataFrame({'Εμπειρία': [5, 10, 15]})
predictions = model.get_prediction(new_data)
pred_summary = predictions.summary_frame(alpha=0.05)
print(f"\nΠροβλέψεις μισθού:")
for i, years in enumerate([5, 10, 15]):
row = pred_summary.iloc[i]
print(f" {years} έτη: {row['mean']:.0f}€ "
f"(95% CI: [{row['obs_ci_lower']:.0f}€, {row['obs_ci_upper']:.0f}€])")
Η μέθοδος get_prediction() παρέχει τόσο τα διαστήματα εμπιστοσύνης της μέσης πρόβλεψης (mean_ci_lower/upper) όσο και τα διαστήματα πρόβλεψης για μεμονωμένες παρατηρήσεις (obs_ci_lower/upper). Τα δεύτερα είναι πάντα ευρύτερα — κάτι λογικό, αφού προβλέπουμε μια μεμονωμένη τιμή αντί για τον μέσο όρο.
Ανάλυση Συσχέτισης
Η ανάλυση συσχέτισης μετρά τον βαθμό και την κατεύθυνση της σχέσης μεταξύ δύο μεταβλητών. Οι δύο βασικοί συντελεστές είναι ο Pearson (για γραμμικές σχέσεις κανονικά κατανεμημένων δεδομένων) και ο Spearman (μη παραμετρικός, για μονοτονικές σχέσεις).
np.random.seed(42)
# Δημιουργία δεδομένων με διαφορετικούς βαθμούς συσχέτισης
n = 100
hours_study = np.random.uniform(1, 10, n)
grade = 4 + 0.5 * hours_study + np.random.normal(0, 0.8, n)
satisfaction = np.random.uniform(1, 10, n) # ασυσχέτιστο
df_corr = pd.DataFrame({
'Ώρες_Μελέτης': hours_study,
'Βαθμός': grade.clip(0, 10),
'Ικανοποίηση': satisfaction
})
# --- Συντελεστής Pearson ---
r_pearson, p_pearson = stats.pearsonr(df_corr['Ώρες_Μελέτης'], df_corr['Βαθμός'])
print(f"Pearson r (Ώρες-Βαθμός): {r_pearson:.4f}, p-value: {p_pearson:.6f}")
r_no_corr, p_no_corr = stats.pearsonr(df_corr['Ώρες_Μελέτης'], df_corr['Ικανοποίηση'])
print(f"Pearson r (Ώρες-Ικανοποίηση): {r_no_corr:.4f}, p-value: {p_no_corr:.4f}")
# --- Συντελεστής Spearman ---
rho, p_spearman = stats.spearmanr(df_corr['Ώρες_Μελέτης'], df_corr['Βαθμός'])
print(f"\nSpearman ρ (Ώρες-Βαθμός): {rho:.4f}, p-value: {p_spearman:.6f}")
# --- Πίνακας Συσχέτισης ---
correlation_matrix = df_corr.corr(method='pearson')
print(f"\nΠίνακας Συσχέτισης Pearson:")
print(correlation_matrix.round(4))
Ο συντελεστής συσχέτισης κυμαίνεται από -1 (τέλεια αρνητική σχέση) έως +1 (τέλεια θετική). Το 0 σημαίνει απουσία γραμμικής σχέσης. Ένας κανόνας-γενικά: τιμές |r| > 0.7 θεωρούνται ισχυρή συσχέτιση, 0.4-0.7 μέτρια, και κάτω από 0.4 ασθενής.
Αλλά προσοχή: συσχέτιση δεν σημαίνει αιτιότητα. Αυτό είναι κάτι που ακούτε συνέχεια, αλλά πιστέψτε με, αξίζει να το θυμάστε κάθε φορά που ερμηνεύετε αποτελέσματα.
# Οπτικοποίηση πίνακα συσχέτισης ως heatmap
fig, ax = plt.subplots(figsize=(8, 6))
im = ax.imshow(correlation_matrix.values, cmap='RdBu_r', vmin=-1, vmax=1)
# Ετικέτες
ax.set_xticks(range(len(correlation_matrix.columns)))
ax.set_yticks(range(len(correlation_matrix.columns)))
ax.set_xticklabels(correlation_matrix.columns, rotation=45, ha='right')
ax.set_yticklabels(correlation_matrix.columns)
# Τιμές στα κελιά
for i in range(len(correlation_matrix)):
for j in range(len(correlation_matrix)):
ax.text(j, i, f'{correlation_matrix.iloc[i, j]:.2f}',
ha='center', va='center', fontsize=12, fontweight='bold')
plt.colorbar(im, label='Συντελεστής Pearson')
plt.title('Πίνακας Συσχέτισης')
plt.tight_layout()
plt.savefig('correlation_heatmap.png', dpi=150)
plt.show()
Για πιο εντυπωσιακά heatmaps, μπορείτε να χρησιμοποιήσετε τη seaborn με τη sns.heatmap(). Δίνει αυτόματα χρωματική κωδικοποίηση και annotation, χωρίς όλο τον χειρωνακτικό κώδικα που γράψαμε παραπάνω.
Νέα Χαρακτηριστικά SciPy 1.17 (2026)
Η έκδοση 1.17 της SciPy (Ιανουάριος 2026) φέρνει κάποιες πραγματικά ενδιαφέρουσες βελτιώσεις. Ας δούμε τις σημαντικότερες:
- Βελτιωμένη υποστήριξη Array API και GPU: Η SciPy 1.17 επεκτείνει τη συμβατότητα με το Python Array API standard, επιτρέποντας χρήση arrays από CuPy, PyTorch και JAX σε περισσότερα modules. Στην πράξη; Μπορείτε να τρέξετε στατιστικούς υπολογισμούς σε GPU χωρίς αλλαγές στον κώδικά σας.
- Αραιοί πίνακες πολλαπλών διαστάσεων: Τα νέα sparse arrays υποστηρίζουν πλέον πάνω από δύο διαστάσεις — μια πολυαναμενόμενη δυνατότητα που ανοίγει πόρτες σε τανυστικούς υπολογισμούς.
- Batch support στο linalg: Οι συναρτήσεις γραμμικής άλγεβρας δέχονται πλέον batch processing. Ιδιαίτερα χρήσιμο αν δουλεύετε με εφαρμογές μηχανικής μάθησης.
- ARPACK σε C: Η μεταφορά από Fortran σε C εξασφαλίζει πλήρη αναπαραγωγιμότητα αποτελεσμάτων μεταξύ πλατφορμών — κρίσιμο για επιστημονικές δημοσιεύσεις.
# Παράδειγμα: Έλεγχος υποστήριξης Array API στη SciPy 1.17
import scipy
print(f"SciPy version: {scipy.__version__}")
# Η SciPy 1.17 υποστηρίζει array backends μέσω του Array API
# Μπορείτε να χρησιμοποιήσετε CuPy arrays για GPU υπολογισμούς
# import cupy as cp
# gpu_data = cp.random.normal(size=10000)
# result = stats.describe(gpu_data) # Εκτέλεση σε GPU
Συχνές Ερωτήσεις (FAQ)
Ποια είναι η διαφορά μεταξύ SciPy και statsmodels;
Αυτή η ερώτηση έρχεται πάντα. Εν συντομία: η SciPy είναι μια γενικής χρήσης βιβλιοθήκη επιστημονικών υπολογισμών με στατιστικές συναρτήσεις στο scipy.stats — ελέγχους υποθέσεων, κατανομές, βασικές λειτουργίες. Η statsmodels είναι εξειδικευμένη στη στατιστική μοντελοποίηση: παλινδρόμηση, χρονοσειρές (ARIMA), μικτά μοντέλα, αναλυτικοί πίνακες αποτελεσμάτων.
Στην πράξη, χρησιμοποιήστε SciPy για ελέγχους υποθέσεων και κατανομές, statsmodels για μοντέλα και παλινδρόμηση. Συχνά χρειάζεστε και τα δύο μαζί.
Πώς επιλέγω τον κατάλληλο στατιστικό έλεγχο;
Εξαρτάται από τρία πράγματα: (1) τον τύπο δεδομένων (συνεχή ή κατηγορικά), (2) τον αριθμό ομάδων που συγκρίνετε, και (3) αν τα δεδομένα ακολουθούν κανονική κατανομή.
Δύο ομάδες + κανονικά δεδομένα = t-test. Τρεις ή περισσότερες ομάδες = ANOVA. Κατηγορικά δεδομένα = χ². Μη κανονικά δεδομένα = μη παραμετρικοί έλεγχοι (Mann-Whitney, Kruskal-Wallis). Ο πίνακας σύνοψης παραπάνω μπορεί να σας βοηθήσει.
Μπορώ να χρησιμοποιήσω Python αντί για R στη στατιστική ανάλυση;
Απολύτως. Η Python με SciPy, statsmodels, pandas και scikit-learn καλύπτει τη συντριπτική πλειονότητα αυτών που κάνατε (ή θα κάνατε) σε R. Η R εξακολουθεί να έχει πλεονέκτημα σε ορισμένους εξειδικευμένους τομείς — μικτά μοντέλα, Bayesian ανάλυση με πακέτα σαν το brms, και φυσικά το πλούσιο CRAN.
Ωστόσο, η Python υπερτερεί στην ενσωμάτωση με production συστήματα, στη μηχανική μάθηση και στην ευρύτερη ανάπτυξη λογισμικού. Σήμερα, η πλειοψηφία των data scientists χρησιμοποιεί Python ως κύρια γλώσσα — κι αυτό δεν είναι τυχαίο.
Τι είναι η τιμή p (p-value) και πώς ερμηνεύεται;
Η τιμή p αντιπροσωπεύει την πιθανότητα να παρατηρήσουμε ένα αποτέλεσμα τόσο ακραίο όσο αυτό που μετρήσαμε, δεδομένου ότι η μηδενική υπόθεση (H₀) ισχύει. Δεν μας λέει αν η H₀ είναι αληθής ή ψευδής — μας λέει πόσο ασυμβίβαστα είναι τα δεδομένα μας με αυτήν.
Η συμβατική ερμηνεία: αν p < 0.05, απορρίπτουμε τη H₀ σε επίπεδο σημαντικότητας 5%. Μικρότερες τιμές p = ισχυρότερη ένδειξη κατά της H₀.
Αλλά — κι αυτό είναι σημαντικό — η στατιστική σημαντικότητα δεν ισοδυναμεί πάντα με πρακτική σημαντικότητα. Με μεγάλα δείγματα, ακόμα και πολύ μικρές τιμές p μπορεί να αντιστοιχούν σε ελάχιστες πρακτικές διαφορές. Εξετάζετε πάντα και το μέγεθος επίδρασης (effect size).
Πώς χειρίζομαι δεδομένα που δεν ακολουθούν κανονική κατανομή;
Υπάρχουν τρεις κύριες στρατηγικές. Πρώτα, ελέγξτε αν όντως δεν είναι κανονικά — με Shapiro-Wilk (stats.shapiro()) ή D'Agostino-Pearson (stats.normaltest()).
Αν δεν είναι, έχετε τρεις επιλογές:
- Μετασχηματισμοί: Λογαρίθμηση (
np.log()) ή Box-Cox (stats.boxcox()) για να προσεγγίσετε κανονική κατανομή. - Μη παραμετρικοί έλεγχοι: Mann-Whitney αντί t-test, Kruskal-Wallis αντί ANOVA, Spearman αντί Pearson.
- Bootstrap μέθοδοι: Η
scipy.stats.bootstrap()δεν κάνει καμία υπόθεση για την κατανομή — πολύ βολική λύση.
# Έλεγχος κανονικότητας και εναλλακτικές λύσεις
data = np.random.exponential(scale=2, size=100)
# Έλεγχος Shapiro-Wilk
stat, p_value = stats.shapiro(data)
print(f"Shapiro-Wilk: statistic={stat:.4f}, p-value={p_value:.4f}")
if p_value < 0.05:
print("Τα δεδομένα ΔΕΝ ακολουθούν κανονική κατανομή.")
print("Χρησιμοποιούμε μη παραμετρικό έλεγχο...")
# Mann-Whitney U αντί t-test
group1 = np.random.exponential(scale=2, size=50)
group2 = np.random.exponential(scale=2.5, size=50)
u_stat, p_mw = stats.mannwhitneyu(group1, group2, alternative='two-sided')
print(f"Mann-Whitney U: statistic={u_stat:.4f}, p-value={p_mw:.4f}")
# Μετασχηματισμός Box-Cox
data_positive = data[data > 0]
transformed, lambda_param = stats.boxcox(data_positive)
print(f"Box-Cox λ: {lambda_param:.4f}")