NumPy στην Python: Οδηγός με Πίνακες, Broadcasting και Παραδείγματα

Μάθε NumPy 2.4 στην Python με πρακτικά παραδείγματα: δημιουργία πινάκων ndarray, indexing, broadcasting, γραμμική άλγεβρα, vectorization και τα νέα χαρακτηριστικά της έκδοσης 2.4 (2026).

Τι Είναι το NumPy και Γιατί το Χρειάζεσαι

Αν ασχολείσαι με data science ή αριθμητικούς υπολογισμούς στην Python, σίγουρα έχεις πέσει πάνω στο NumPy. Και δεν είναι τυχαίο — είναι κυριολεκτικά η βιβλιοθήκη πάνω στην οποία χτίζονται τα πάντα: Pandas, scikit-learn, TensorFlow, PyTorch, Matplotlib. Χωρίς υπερβολή, το NumPy είναι η βάση του οικοσυστήματος.

Η καρδιά του είναι ο πολυδιάστατος πίνακας ndarray, που αποθηκεύει δεδομένα σε συνεχόμενη μνήμη και εκτελεί πράξεις σε βελτιστοποιημένο κώδικα C. Το αποτέλεσμα; Ταχύτητες που αφήνουν τις Python λίστες στη σκόνη.

Σε αυτόν τον οδηγό θα περάσουμε από τα βασικά μέχρι τα προχωρημένα — δημιουργία πινάκων, broadcasting, γραμμική άλγεβρα, στατιστική, και φυσικά τα νέα features του NumPy 2.4 (Ιανουάριος 2026). Όλα με πρακτικά παραδείγματα κώδικα που μπορείς να τρέξεις αμέσως.

Εγκατάσταση και Πρώτα Βήματα

Εγκατάσταση του NumPy 2.4

Η τελευταία σταθερή έκδοση είναι η NumPy 2.4.1 (Ιανουάριος 2026). Η εγκατάσταση είναι στάνταρ — τίποτα περίεργο:

pip install numpy==2.4.1

# ή μέσω conda
conda install numpy=2.4.1

Εισαγωγή και Επαλήθευση

import numpy as np

print(np.__version__)  # 2.4.1
print(np.show_config())

Η σύμβαση import numpy as np είναι καθολική — θα τη δεις παντού. Σε tutorials, σε papers, σε production κώδικα. Απλά χρησιμοποίησέ τη κι εσύ.

Δημιουργία Πινάκων (ndarray)

Ο πίνακας ndarray είναι η καρδιά του NumPy. Σε αντίθεση με τις λίστες της Python, αποθηκεύει δεδομένα ομοιογενούς τύπου σε συνεχόμενες θέσεις μνήμης. Αυτό τον κάνει έως και 50 φορές ταχύτερο — ναι, δεν το λέω στο περίπου.

Από Λίστα Python

Ο πιο απλός τρόπος να φτιάξεις ένα array:

# Μονοδιάστατος πίνακας
a = np.array([1, 2, 3, 4, 5])
print(a)          # [1 2 3 4 5]
print(type(a))    # 

# Δισδιάστατος πίνακας (μήτρα)
matrix = np.array([[1, 2, 3],
                   [4, 5, 6],
                   [7, 8, 9]])
print(matrix.shape)  # (3, 3)

Ενσωματωμένες Συναρτήσεις Δημιουργίας

Φυσικά, δε θα φτιάχνεις πάντα arrays από λίστες. Το NumPy σου δίνει μια σειρά από εύχρηστες συναρτήσεις:

# Πίνακας μηδενικών
zeros = np.zeros((3, 4))

# Πίνακας μονάδων
ones = np.ones((2, 3), dtype=np.float32)

# Μοναδιαίος πίνακας (ταυτοτικός)
identity = np.eye(4)

# Πίνακας με σταθερή τιμή
full = np.full((3, 3), fill_value=7)

# Διάστημα τιμών
linspace = np.linspace(0, 1, 11)    # 11 τιμές από 0 έως 1
arange = np.arange(0, 10, 0.5)      # βήμα 0.5

# Τυχαίοι αριθμοί (νέο API)
rng = np.random.default_rng(seed=42)
random_arr = rng.standard_normal((3, 4))
random_int = rng.integers(0, 100, size=(5,))

Νέο στο NumPy 2.4: Παράμετρος ndmax

Μια ενδιαφέρουσα προσθήκη — η παράμετρος ndmax σου δίνει έλεγχο στο πόσες διαστάσεις θα δημιουργηθούν από ένθετες ακολουθίες:

# Περιορισμός μέγιστων διαστάσεων σε ένθετες ακολουθίες
result = np.array([[1, 2], [3, 4]], dtype=object, ndmax=1)
print(result)       # [list([1, 2]) list([3, 4])]
print(result.shape) # (2,)

Ιδιότητες και Τύποι Δεδομένων (dtypes)

Κάθε ndarray έχει μερικές βασικές ιδιότητες που αξίζει να γνωρίζεις. Ειδικά αν κάνεις debugging, αυτές θα σε σώσουν:

arr = np.array([[1.5, 2.3, 3.1],
                [4.7, 5.9, 6.2]])

print(arr.ndim)     # 2 — αριθμός διαστάσεων
print(arr.shape)    # (2, 3) — σχήμα
print(arr.size)     # 6 — συνολικά στοιχεία
print(arr.dtype)    # float64 — τύπος δεδομένων
print(arr.itemsize) # 8 — bytes ανά στοιχείο
print(arr.nbytes)   # 48 — συνολική μνήμη

Μετατροπή Τύπων (Casting)

# Βασική μετατροπή
float_arr = np.array([1, 2, 3], dtype=np.float64)
int_arr = float_arr.astype(np.int32)

# Νέο στο 2.4: casting='same_value'
# Εξασφαλίζει ότι η μετατροπή δεν αλλάζει τιμές
arr = np.array([1.0, 2.0, 3.0])
safe = arr.astype(np.int32, casting='same_value')  # OK

arr2 = np.array([1.5, 2.7])
# arr2.astype(np.int32, casting='same_value')  # ValueError!

Αυτό το casting='same_value' είναι ειλικρινά πολύ χρήσιμο. Σε προηγούμενες εκδόσεις, αν μετέτρεπες ένα float σε int, μπορούσες να χάσεις δεδομένα χωρίς καν warning. Τώρα μπορείς να πιάσεις αυτά τα σφάλματα εγκαίρως.

Indexing και Slicing

Λοιπόν, ας μπούμε στο ψητό. Το indexing στο NumPy μοιάζει με αυτό της Python, αλλά πηγαίνει πολύ παραπέρα.

Βασικό Indexing

arr = np.array([10, 20, 30, 40, 50])

print(arr[0])      # 10
print(arr[-1])     # 50
print(arr[1:4])    # [20 30 40]
print(arr[::2])    # [10 30 50] — κάθε δεύτερο

Indexing σε Πολυδιάστατους Πίνακες

matrix = np.array([[1, 2, 3],
                   [4, 5, 6],
                   [7, 8, 9]])

print(matrix[0, 1])     # 2 — γραμμή 0, στήλη 1
print(matrix[1:, :2])   # [[4, 5], [7, 8]] — υποπίνακας
print(matrix[:, 2])     # [3, 6, 9] — ολόκληρη η στήλη 2

Boolean Indexing (Μάσκες)

Εδώ αρχίζει η μαγεία. Μπορείς να φιλτράρεις δεδομένα απευθείας με συνθήκες:

data = np.array([15, 22, 8, 35, 12, 41, 3])

# Φιλτράρισμα με συνθήκη
mask = data > 20
print(mask)        # [False  True False  True False  True False]
print(data[mask])  # [22 35 41]

# Σύνθετες συνθήκες
result = data[(data > 10) & (data < 40)]
print(result)      # [15 22 35 12]

Fancy Indexing

arr = np.array([10, 20, 30, 40, 50])

# Επιλογή συγκεκριμένων θέσεων
indices = [0, 2, 4]
print(arr[indices])  # [10 30 50]

# Σε 2D πίνακες
matrix = np.arange(12).reshape(3, 4)
rows = np.array([0, 2])
cols = np.array([1, 3])
print(matrix[rows, cols])  # [1, 11]

Μια σημαντική λεπτομέρεια: Το βασικό indexing (slices) επιστρέφει view — δηλαδή αναφορά στα ίδια δεδομένα. Αν αλλάξεις κάτι στο view, αλλάζει και ο αρχικός πίνακας. Αντίθετα, το fancy indexing επιστρέφει αντίγραφο. Αυτή η διάκριση μπορεί να σε σώσει (ή να σε μπλέξει) σε production κώδικα.

Vectorized Πράξεις

Η vectorization είναι ο βασικός λόγος που χρησιμοποιούμε NumPy. Αντί να γράφεις αργούς Python βρόχους, εκτελείς πράξεις σε ολόκληρους πίνακες ταυτόχρονα. Κάτω από το καπό, τρέχει βελτιστοποιημένος κώδικας C.

Αριθμητικές Πράξεις

a = np.array([1, 2, 3, 4])
b = np.array([10, 20, 30, 40])

print(a + b)     # [11 22 33 44]
print(a * b)     # [10 40 90 160]
print(a ** 2)    # [1 4 9 16]
print(np.sqrt(b)) # [3.16... 4.47... 5.47... 6.32...]

Σύγκριση Ταχύτητας: Loop vs Vectorization

Δε θα σε πείσω μόνο με λόγια. Δοκίμασε αυτό:

import time

size = 1_000_000
a = np.random.rand(size)
b = np.random.rand(size)

# Βρόχος Python
start = time.perf_counter()
result_loop = [a[i] + b[i] for i in range(size)]
loop_time = time.perf_counter() - start

# Vectorized NumPy
start = time.perf_counter()
result_vec = a + b
vec_time = time.perf_counter() - start

print(f"Loop: {loop_time:.4f}s")
print(f"NumPy: {vec_time:.4f}s")
print(f"Speedup: {loop_time / vec_time:.0f}x")

Τυπικά θα δεις speedup 50–100x. Η πρώτη φορά που έτρεξα αυτό το benchmark ομολογώ ότι εντυπωσιάστηκα.

Universal Functions (ufuncs)

Οι ufuncs είναι συναρτήσεις που εφαρμόζονται στοιχείο-προς-στοιχείο, υλοποιημένες σε C:

arr = np.array([0, np.pi/6, np.pi/4, np.pi/3, np.pi/2])

print(np.sin(arr))   # τριγωνομετρικό ημίτονο
print(np.exp(arr))   # εκθετική
print(np.log(arr+1)) # φυσικός λογάριθμος

# Στο NumPy 2.4, οι scalar ufuncs είναι ~6x ταχύτερες
import math
# np.sin(0.5) τώρα μόνο 3x πιο αργό από math.sin(0.5)
# (ήταν 19x πιο αργό σε παλαιότερες εκδόσεις)

Αυτή η βελτίωση στο 2.4 μπορεί να φαίνεται μικρή, αλλά αν καλείς ufuncs σε scalars μέσα σε loops (κάτι που δυστυχώς γίνεται πιο συχνά απ' ό,τι θα έπρεπε), η διαφορά είναι αισθητή.

Broadcasting: Η Μαγεία του NumPy

Εδώ είναι που πολλοί αρχάριοι μπερδεύονται — και δικαιολογημένα. Το broadcasting επιτρέπει πράξεις μεταξύ πινάκων με διαφορετικά σχήματα, χωρίς ρητή αντιγραφή δεδομένων. Ακούγεται μαγικό; Κάπως είναι.

Οι Τρεις Κανόνες του Broadcasting

Τους αξίζει να τους αποστηθίσεις:

  1. Κανόνας 1: Αν οι πίνακες έχουν διαφορετικό αριθμό διαστάσεων, ο μικρότερος «γεμίζει» με 1 στα αριστερά.
  2. Κανόνας 2: Αν κάποια διάσταση έχει μέγεθος 1, «τεντώνεται» για να ταιριάξει.
  3. Κανόνας 3: Αν καμία διάσταση δεν είναι 1 και τα μεγέθη δεν ταιριάζουν — σφάλμα.

Πρακτικά Παραδείγματα

# Παράδειγμα 1: Πίνακας + Scalar
arr = np.array([[1, 2, 3],
                [4, 5, 6]])
print(arr + 10)
# [[11 12 13]
#  [14 15 16]]

# Παράδειγμα 2: Πίνακας (2,3) + Διάνυσμα (3,)
row_vector = np.array([100, 200, 300])
print(arr + row_vector)
# [[101 202 303]
#  [104 205 306]]

# Παράδειγμα 3: Στήλη (3,1) + Γραμμή (1,4)
col = np.array([[0], [10], [20]])
row = np.array([1, 2, 3, 4])
print(col + row)
# [[ 1  2  3  4]
#  [11 12 13 14]
#  [21 22 23 24]]

Πρακτική Εφαρμογή: Κανονικοποίηση Δεδομένων

Ένα κλασικό real-world σενάριο — Z-score normalization:

# Z-score normalization με broadcasting
data = rng.standard_normal((1000, 5))  # 1000 δείγματα, 5 features

mean = data.mean(axis=0)   # (5,)
std = data.std(axis=0)     # (5,)

# Broadcasting: (1000,5) - (5,) → (1000,5)
normalized = (data - mean) / std

print(f"Μέσος: {normalized.mean(axis=0).round(2)}")  # [0. 0. 0. 0. 0.]
print(f"Τυπ. απόκλ.: {normalized.std(axis=0).round(2)}")  # [1. 1. 1. 1. 1.]

Χωρίς broadcasting, θα χρειαζόσουν ένα loop πάνω στις 1000 γραμμές. Με broadcasting, δύο γραμμές κώδικα — και είναι 8x+ πιο γρήγορο.

Αναδιαμόρφωση Πινάκων (Reshaping)

Το reshaping είναι κάτι που θα κάνεις συνεχώς, ειδικά αν δουλεύεις με ML μοντέλα (που θέλουν τα δεδομένα σε πολύ συγκεκριμένα σχήματα).

arr = np.arange(12)
print(arr)  # [ 0  1  2  3  4  5  6  7  8  9 10 11]

# Reshape σε 2D
reshaped = arr.reshape(3, 4)
print(reshaped)
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]]

# Χρήση -1 για αυτόματο υπολογισμό
auto = arr.reshape(4, -1)  # (4, 3)

# Flatten vs Ravel
flat = reshaped.flatten()   # νέο αντίγραφο
rav = reshaped.ravel()      # view (πιο αποδοτικό)

# Transpose
transposed = reshaped.T
print(transposed.shape)  # (4, 3)

# Προσθήκη διάστασης
vec = np.array([1, 2, 3])
col_vec = vec[:, np.newaxis]     # (3, 1)
row_vec = vec[np.newaxis, :]     # (1, 3)

Μικρό tip: Προτίμησε ravel() αντί flatten() εκτός αν θέλεις ρητά αντίγραφο. Η ravel() επιστρέφει view (χωρίς αντιγραφή μνήμης), οπότε είναι πιο αποδοτική.

Στοίβαξη και Διαχωρισμός

a = np.array([1, 2, 3])
b = np.array([4, 5, 6])

# Στοίβαξη
print(np.vstack([a, b]))    # [[1,2,3],[4,5,6]]
print(np.hstack([a, b]))    # [1,2,3,4,5,6]
print(np.column_stack([a, b]))  # [[1,4],[2,5],[3,6]]

# Διαχωρισμός
arr = np.arange(16).reshape(4, 4)
top, bottom = np.vsplit(arr, 2)
left, right = np.hsplit(arr, 2)

Γραμμική Άλγεβρα με np.linalg

Αν ασχολείσαι με machine learning ή στατιστική, η γραμμική άλγεβρα είναι ψωμοτύρι σου. Το NumPy σου δίνει ένα πλήρες toolkit μέσω του np.linalg.

A = np.array([[2, 1],
              [1, 3]])
b = np.array([5, 7])

# Γινόμενο πινάκων
C = A @ A          # ή np.matmul(A, A)
print(C)           # [[ 5  5], [ 5 10]]

# Ορίζουσα
det = np.linalg.det(A)
print(f"det(A) = {det:.1f}")  # 5.0

# Αντίστροφος πίνακας
A_inv = np.linalg.inv(A)
print(A_inv @ A)   # [[1, 0], [0, 1]] (μοναδιαίος)

# Επίλυση γραμμικού συστήματος Ax = b
x = np.linalg.solve(A, b)
print(f"Λύση: x = {x}")  # [1.6 1.8]

# Ιδιοτιμές και ιδιοδιανύσματα
eigenvalues, eigenvectors = np.linalg.eig(A)
print(f"Ιδιοτιμές: {eigenvalues}")

# SVD (Singular Value Decomposition)
U, S, Vt = np.linalg.svd(A)

# Νέες συναρτήσεις (NumPy 2.2+): matvec και vecmat
v = np.array([1, 2])
print(np.matvec(A, v))  # γινόμενο πίνακα-διανύσματος

Προσωπικά, χρησιμοποιώ πιο συχνά τα solve() και svd() από οτιδήποτε άλλο στο np.linalg. Αν αρχίζεις τώρα, εστίασε σε αυτά.

Στατιστικές Συναρτήσεις

Δεν χρειάζεσαι πάντα Pandas για βασική στατιστική. Το NumPy τα καταφέρνει μια χαρά από μόνο του:

data = rng.standard_normal((100, 3))

# Βασική στατιστική
print(f"Μέσος:    {np.mean(data, axis=0)}")
print(f"Διάμεσος: {np.median(data, axis=0)}")
print(f"Τυπ. απ.: {np.std(data, axis=0)}")
print(f"Διακύμ.:  {np.var(data, axis=0)}")

# Εύρος τιμών
print(f"Min: {np.min(data, axis=0)}")
print(f"Max: {np.max(data, axis=0)}")

# Εκατοστημόρια
q25, q50, q75 = np.percentile(data, [25, 50, 75], axis=0)
print(f"IQR: {q75 - q25}")

# Πίνακας συσχέτισης
corr = np.corrcoef(data.T)
print(f"Συσχέτιση:\n{corr.round(3)}")

# Αθροιστικές συναρτήσεις
cumsum = np.cumsum(data[:, 0])
cumprod = np.cumprod(np.abs(data[:5, 0]))

Πρακτικό Παράδειγμα: Εντοπισμός Outliers

Ένα κλασικό πρόβλημα — βρες τα outliers με Z-score:

def detect_outliers_zscore(data, threshold=3):
    """Εντοπισμός outliers με Z-score"""
    mean = np.mean(data, axis=0)
    std = np.std(data, axis=0)
    z_scores = np.abs((data - mean) / std)
    outlier_mask = np.any(z_scores > threshold, axis=1)
    return outlier_mask

# Εφαρμογή
outliers = detect_outliers_zscore(data)
print(f"Βρέθηκαν {outliers.sum()} outliers από {len(data)} δείγματα")

Τυχαίοι Αριθμοί: Νέο Generator API

Αν χρησιμοποιείς ακόμα np.random.rand() και np.random.randn(), ήρθε η ώρα να αναβαθμιστείς. Από το NumPy 1.17+, το νέο Generator API είναι ο συνιστώμενος τρόπος:

rng = np.random.default_rng(seed=2026)

# Βασικές κατανομές
uniform = rng.uniform(0, 1, size=1000)
normal = rng.normal(loc=0, scale=1, size=1000)
integers = rng.integers(1, 100, size=50)

# Κατανομή Poisson (π.χ. αριθμός γεγονότων/ώρα)
poisson = rng.poisson(lam=5, size=1000)

# Ανάμιξη και δειγματοληψία
arr = np.arange(10)
rng.shuffle(arr)                    # ανάμιξη in-place
sample = rng.choice(arr, size=5, replace=False)  # δείγμα χωρίς επανάθεση

# Αναπαραγωγιμότητα
rng1 = np.random.default_rng(42)
rng2 = np.random.default_rng(42)
assert np.array_equal(rng1.random(10), rng2.random(10))

Γιατί το νέο API; Καλύτερη στατιστική ποιότητα, thread safety, και πιο ξεκάθαρη αναπαραγωγιμότητα. Αξίζει τη μετάβαση.

Δομημένοι Πίνακες (Structured Arrays)

Αυτό είναι ένα feature που πολλοί δεν ξέρουν ότι υπάρχει. Οι δομημένοι πίνακες σου επιτρέπουν να αποθηκεύεις ετερογενή δεδομένα (strings, ints, floats) σε ένα μόνο ndarray — σαν ένα lightweight DataFrame:

# Ορισμός δομής
dt = np.dtype([
    ('name', 'U20'),        # Unicode string, 20 χαρακτήρες
    ('age', np.int32),       # Ακέραιος
    ('salary', np.float64)   # Δεκαδικός
])

# Δημιουργία
employees = np.array([
    ('Μαρία', 32, 52000.0),
    ('Γιώργος', 28, 45000.0),
    ('Ελένη', 35, 61000.0),
    ('Νίκος', 41, 58000.0)
], dtype=dt)

# Πρόσβαση σε πεδία
print(employees['name'])    # ['Μαρία' 'Γιώργος' 'Ελένη' 'Νίκος']
print(employees['salary'])  # [52000. 45000. 61000. 58000.]

# Φιλτράρισμα
high_salary = employees[employees['salary'] > 50000]
print(high_salary['name'])  # ['Μαρία' 'Ελένη' 'Νίκος']

# Μέσος μισθός
print(f"Μέσος μισθός: {employees['salary'].mean():.0f}€")

Πότε να τα χρησιμοποιήσεις; Όταν θέλεις ελαφριά αποθήκευση ετερογενών δεδομένων χωρίς να φορτώσεις ολόκληρο το Pandas. Για γρήγορα scripts και prototyping, είναι τέλειοι.

np.where: Το Ελβετικό Σουγιά

Η np.where() είναι από τις πιο χρήσιμες συναρτήσεις. Τη χρησιμοποιείς παντού:

scores = np.array([85, 42, 91, 67, 38, 73, 55])

# Αντικατάσταση βάσει συνθήκης
result = np.where(scores >= 50, 'Pass', 'Fail')
print(result)  # ['Pass' 'Fail' 'Pass' 'Pass' 'Fail' 'Pass' 'Pass']

# Εύρεση θέσεων
positions = np.where(scores > 80)
print(positions)  # (array([0, 2]),)

# Ένθετες συνθήκες με np.select
conditions = [
    scores >= 90,
    scores >= 70,
    scores >= 50
]
grades = ['A', 'B', 'C']
result = np.select(conditions, grades, default='F')
print(result)  # ['B' 'F' 'A' 'C' 'F' 'B' 'C']

NumPy vs Pandas vs Λίστες: Πότε Χρησιμοποιείς Τι

Αυτή είναι μια από τις πιο συχνές ερωτήσεις, ειδικά από αρχάριους. Η απάντηση δεν είναι πάντα ξεκάθαρη, αλλά αυτός ο πίνακας βοηθάει:

ΧαρακτηριστικόΛίστα PythonNumPyPandas
ΤαχύτηταΑργήΠολύ γρήγοροΓρήγορο
ΜνήμηΥψηλήΧαμηλήΜέτρια
Τύποι δεδομένωνΜικτοίΟμοιογενείςΕτερογενείς
Ιδανικό μέγεθοςΜικρά δεδομέναΜικρά-ΜεσαίαΜεγάλα
Ιδανικό γιαΓενικός κώδικαςΑριθμητική, MLΑνάλυση, ETL

Γενικός κανόνας: NumPy για αριθμητικούς υπολογισμούς, γραμμική άλγεβρα και input σε ML μοντέλα. Pandas για εξερεύνηση, καθαρισμό και ETL. Για μικρά, απλά δεδομένα, μια λίστα Python μπορεί να αρκεί.

Τι Νέο στο NumPy 2.4 (2026)

Η έκδοση 2.4 κυκλοφόρησε τον Δεκέμβριο 2025, με patch 2.4.1 τον Ιανουάριο 2026. Φέρνει αρκετές αξιοσημείωτες βελτιώσεις:

Βελτιώσεις Απόδοσης

  • np.unique σε strings: Έως 15x ταχύτερο χάρη σε hash-based αλγόριθμο
  • Scalar ufuncs: ~6x ταχύτερα (π.χ. np.sin(scalar))
  • np.ndindex: 5.2x ταχύτερο με χρήση itertools.product
  • np.quantile/np.percentile: Βελτιωμένη ακρίβεια για float16 και float32

Νέα Χαρακτηριστικά

  • casting='same_value': Ασφαλής μετατροπή τύπων χωρίς απώλεια
  • ndmax στο np.array(): Έλεγχος μέγιστων διαστάσεων
  • Πρωτόκολλο __numpy_dtype__: Νέος τρόπος ορισμού dtypes
  • Runtime signature introspection: Η inspect.signature() λειτουργεί σε 300+ συναρτήσεις
  • Ανανεωμένο numpy.finfo: Σταθερές απευθείας από C macros

Αφαιρέσεις (Breaking Changes)

Πρόσεξε αυτά αν αναβαθμίζεις:

  • numpy.trapz → χρησιμοποίησε numpy.trapezoid
  • numpy.in1d → χρησιμοποίησε numpy.isin
  • Η παράμετρος newshape στο reshape αφαιρέθηκε — χρησιμοποίησε shape=

Πρακτικό Project: Ανάλυση Δεδομένων Καιρού

Ας βάλουμε τα πάντα μαζί με ένα ολοκληρωμένο παράδειγμα. Θα προσομοιώσουμε δεδομένα θερμοκρασίας για τρεις ελληνικές πόλεις και θα τα αναλύσουμε:

import numpy as np

rng = np.random.default_rng(seed=2026)

# Προσομοίωση δεδομένων θερμοκρασίας (365 ημέρες, 3 πόλεις)
days = 365
cities = ['Αθήνα', 'Θεσσαλονίκη', 'Ηράκλειο']

# Βασική θερμοκρασία + εποχιακή διακύμανση + θόρυβος
t = np.arange(days)
seasonal = 10 * np.sin(2 * np.pi * t / 365)  # εποχιακή

base_temps = np.array([18, 15, 20])  # μέσες ετήσιες
noise = rng.normal(0, 3, (days, 3))

# Broadcasting: (365,1) * (1,) + (3,) + (365,3)
temps = seasonal[:, np.newaxis] + base_temps + noise

# Βασική στατιστική ανά πόλη
print("=== Ετήσια Στατιστικά ===")
for i, city in enumerate(cities):
    col = temps[:, i]
    print(f"\n{city}:")
    print(f"  Μέσος: {col.mean():.1f}°C")
    print(f"  Min:   {col.min():.1f}°C (ημέρα {col.argmin()})")
    print(f"  Max:   {col.max():.1f}°C (ημέρα {col.argmax()})")
    print(f"  Std:   {col.std():.1f}°C")

# Εύρεση ζεστών ημερών (>30°C) ανά πόλη
hot_days = (temps > 30).sum(axis=0)
print(f"\nΖεστές ημέρες (>30°C): {dict(zip(cities, hot_days))}")

# Συσχέτιση μεταξύ πόλεων
corr = np.corrcoef(temps.T)
print(f"\nΣυσχέτιση Αθήνα-Θεσσαλονίκη: {corr[0,1]:.3f}")
print(f"Συσχέτιση Αθήνα-Ηράκλειο: {corr[0,2]:.3f}")

# Κινητός μέσος 7 ημερών
def moving_average(arr, window=7):
    """Υπολογισμός κινητού μέσου με NumPy"""
    kernel = np.ones(window) / window
    return np.convolve(arr, kernel, mode='valid')

ma_athens = moving_average(temps[:, 0])
print(f"\nΜέγεθος κινητού μέσου: {ma_athens.shape[0]} ημέρες")

Αυτό το παράδειγμα χρησιμοποιεί broadcasting, στατιστικές, boolean indexing, και convolution — όλα σε λιγότερο από 40 γραμμές. Αυτή είναι η δύναμη του NumPy.

Συχνές Ερωτήσεις (FAQ)

Ποια είναι η διαφορά μεταξύ NumPy array και λίστας Python;

Τα NumPy arrays αποθηκεύουν ομοιογενή δεδομένα σε συνεχόμενη μνήμη, ενώ οι λίστες αποθηκεύουν αναφορές σε αντικείμενα Python. Πρακτικά, αυτό σημαίνει ότι τα arrays είναι έως 50x ταχύτερα σε αριθμητικές πράξεις και πολύ πιο αποδοτικά σε μνήμη. Οι λίστες είναι πιο ευέλικτες (δέχονται μικτούς τύπους), αλλά ακριβώς αυτή η ευελιξία τις κάνει πιο αργές.

Τι είναι το broadcasting στο NumPy και πώς λειτουργεί;

Το broadcasting είναι ο μηχανισμός που επιτρέπει πράξεις μεταξύ πινάκων με διαφορετικά σχήματα. Ο NumPy «τεντώνει» εικονικά τον μικρότερο πίνακα χωρίς αντιγραφή δεδομένων. Ακολουθεί τρεις κανόνες: συμπλήρωση διαστάσεων με 1 αριστερά, επέκταση διαστάσεων μεγέθους 1, σφάλμα αν δεν ταιριάζουν.

Πώς κάνω εγκατάσταση και αναβάθμιση σε NumPy 2.4;

Τρέξε pip install --upgrade numpy ή conda update numpy. Πρόσεξε ότι το NumPy 2.4 αφαιρεί παλιές συναρτήσεις (trapztrapezoid, in1disin). Καλό είναι να ελέγξεις τον κώδικά σου για breaking changes πριν αναβαθμίσεις — ειδικά αν χρησιμοποιείς εξαρτώμενες βιβλιοθήκες.

Πότε πρέπει να χρησιμοποιήσω NumPy αντί για Pandas;

NumPy για αριθμητικούς υπολογισμούς, γραμμική άλγεβρα, input σε μοντέλα ML, και όταν χρειάζεσαι μέγιστη ταχύτητα. Pandas για ετερογενή δεδομένα, εξερεύνηση, καθαρισμό και ETL. Εμπειρικά, για datasets κάτω από 50K γραμμές το NumPy είναι συνήθως ταχύτερο. Για μεγαλύτερα datasets με πολλά φίλτρα, το Pandas υπερτερεί.

Τι είναι η vectorization και γιατί είναι σημαντική;

Vectorization σημαίνει εκτέλεση πράξεων σε ολόκληρους πίνακες ταυτόχρονα, αντί στοιχείο-στοιχείο με βρόχο. Ο NumPy χρησιμοποιεί εσωτερικά βελτιστοποιημένο κώδικα C, κάνοντας τις vectorized πράξεις 50-100x ταχύτερες. Αν πιάσεις τον εαυτό σου να γράφει for-loops πάνω σε NumPy arrays, σχεδόν πάντα υπάρχει vectorized εναλλακτική.

Σχετικά με τον Συγγραφέα Editorial Team

Our team of expert writers and editors.