numpy
Η βιβλιοθήκη numpy
είναι μια συλλογή από συναρτήσεις και τύπους δεδομένων για τη γλώσσα python
οι οποία έχει φτιαχτεί για να χρησιμοποιείται σε περιπτώσεις πολλών και επαναλαμβανόμενων αριθμητικών υπολογισμών, όπως αυτοί συναντώνται σε μαθηματικές ή επιστημονικές εφαρμογές. Η numpy
έχει φτιαχτεί με σκοπό την αποτελεσματική υλοποίηση κάποιων κοινών υπολογιστικών πράξεων και ο τρόπος που τη χρησιμοποιούμε είναι ότι προσπαθούμε όσο μπορούμε να χρησιμοποιούμε τους τύπους δεδομένων και τις συναρτήσεις της βιβλιοθήκης αυτής και όπου μπορούμε να αποφεύγουμε να υλοποιούμε με δικό μας κώδικα πράγματα που είναι ήδη υλοποιημένα, και μάλιστα με πολύ πιο αποτελεσματικό τρόπο, στη βιβλιοθήκη numpy
.
numpy
και χωρίςΠαρακάτω βλέπουμε δύο διαφορετικές υλοποιήσεις, τη μια χωρίς τη numpy
(συνάρτηση nonumpy_version
) και την άλλη με τη numpy
(numpy_version
) των εξής μαθηματικών πράξεων:
Και οι δύο αυτές συναρτήσεις κάνουν τα παραπάνω 1 και 2 και επίσης χρονομετρούν τον εαυτό τους και επιστρέφουν το χρόνο που τους πήρε να τρέξουν σε δευτερόλεπτα. Παρατηρείστε ότι η μορφή χωρίς numpy
είναι πολλές φορές πιο αργή από τη μορφή με numpy
(το πόσες φορές ακριβώς είναι πιο αργή εξαρτάται και από τον υπολογιστή, αλλά 10 ως 50 φορές είναι συνηθισμένες τιμές).
Για τη συνάρτηση nonumpy_version
δεν έχουμε κάτι νέο να εξηγήσουμε, απλά να πούμε ότι τα διανύσματα \(X, Y, Z\) υλοποιούνται με τις συνηθισμένες μας λίστες της python
.
numpy_version
και τα arrays.Στη συνάρτηση numpy_version
παρατηρούμε κατ' αρχήν ότι χρησιμοποιούμε μια παραλλαγή της γνωστής μας range
τη συνάρτηση numpy.arange
.
Παρατηρούμε επίσης τη γραμμή Z = X+Y
που αν τα \(X, Y\) μας ήταν εδώ λίστες (δεν είναι) τότε το αποτέλεσμα \(Z\) θα ήταν όχι το κατά συντεταγμένη άθροισμα των δύο λιστών αλλά η συνένωσή τους (η Y
μετά τη X
).
Δεν είναι λοιπόν λίστα αυτό που επιστρέφει η συνάρτηση numpy.arange
αλλά ένας νέος τύπος δεδομένων, το numpy.array
(το όνομα array
δηλ. ορίζεται κάπου μέσα στη numpy
). Μπορούμε να σκεφτόμαστε τον τύπο αυτό δεδομένων, όπως έχει υλοποιηθεί στη numpy
, ως μια γενίκευση και ταυτόχρονα διαφορετική υλοποίηση της γνωστής μας λίστας.
Αν λοιπόν τα X
και Y
είναι arrays τότε η εντολή Z = X+Y
παράγει ένα νέο array (στη μεταβλητή Z
) του οποίου τα περιεχόμενα είναι τα κατά συντεταγμένες αθροίσματα των περιεχομένων των X
και Y
. Το γεγονός ότι η python
γνωρίζει πώς τα προσθέτει δύο arrays κατά σημείο οφείλεται στο ότι έχουμε κάνει import numpy
.
import numpy
import time
def nonumpy_version():
t1 = time.time()
X = range(10000000)
Y = range(10000000)
Z = []
for i in range(len(X)):
Z.append(X[i] + Y[i])
return time.time() - t1
def numpy_version():
t1 = time.time()
X = numpy.arange(10000000)
Y = numpy.arange(10000000)
Z = X + Y
return time.time() - t1
print nonumpy_version()
print numpy_version()
Εδώ βλέπουμε ένα νέο τρόπο να κάνουμε import
τη βιβλιοθήκη μας δίνοντάς της ταυτόχρονα ένα μικρότερο όνομα, συνήθως np
, για να μη γράφουμε συνέχει το μακρύτερο όνομα numpy
. Έτσι αν ένα όνομα είναι δηλωμένο μέσα στη numpy
, π.χ. το όνομα array
ή η συνάρτηση arange
, ο τρόπος να αναφερθούμε σε αυτό το όνομα είναι με το πρόθεμα np.
, γράφοντας δηλ. np.array
και np.arange
.
Έπειτα δημιουργούμε μια λίστα l
με περιεχόμενα 1, 2, 3 και χρησιμοποιούμε τη συνάρτηση np.array
για να φτιάξουμε από τη λίστα l
ένα array a
που περιέχει τις ίδιες τιμές. Παρατηρείστε ότι όταν τυπώνουμε το l
και το a
αυτά τυπώνονται κάπως διαφορετικά από την python
αλλά έχουν τα ίδια περιεχόμενα.
Στον ορισμό της μεταβλητής b
βλέπουμε το πώς ορίζουμε ένα διδιάστατο πίνακα με τη numpy
. Περιγράφουμε τις γραμμές του πίνακα σε μια λίστα την κάθε μία και ολόκληρο τον πίνακα ως μια λίστα των γραμμών του. Σε αυτή τη λίστα από λίστες εφαρμόζουμε τη συνάρτηση array
και παράγουμε το διδιάστατο πίνακα b
, τον οποίο και τυπώνουμε.
Για να ορίσουμε τη μεταβλητή c
με τον ανάστροφο πίνακα του b
καλούμε τη μέθοδο transpose()
του πίνακα b
, η οποία επιστρέφει ένα άλλο array που περιέχει τον ανάστροφο του b
. Έπειτα τυπώνουμε το c
.
Τέλος καλούμε τη μέθοδο sum()
του array c
η οποία μας επιστρέφει το άθροισμα όλων των στοιχείων του c
.
import numpy as np
l = [1, 2, 3]
a = np.array(l)
print "l is", l
print "a is", a
b = np.array( [ [1, 2, 3], [4, 5, 6] ] )
print "b is\n", b
c = b.transpose()
print "c is\n", c
print "Sum of elements of c is", np.sum(c)
Εδώ δημιουργούμε ένα array a
από τη λίστα [1, 2, 3]
και προσδιορίζουμε ότι θέλουμε ο τύπος των αριθμών στο array να είναι float64
(αυτό είναι ένα όνομα δηλωμένο στη numpy
άρα πρέπει να γράψουμε np.float64
). Το float64
είναι ένας τύπος δεδομένων (αριθμός κινητής υποδιαστολής με 64 αντί για 32 bits, για να παριστάνουμε πραγματικούς αριθμούς) που δεν είναι ορισμένος στην ίδια την python
.
Όταν στο τέλος λέμε στην python
να τυπώσει τη μεταβλητή a
παρατηρούμε ότι οι αριθμοί 1, 2, 3 εμφανίζονται με υποδιαστολή, έχουν δηλ. μετατραπεί εσωτερικά στον υπολογιστή σε αριθμούς κινητής υποδιαστολής, παρά το ότι τυχαίνει να είναι και ακέραιοι.
import numpy as np
a = np.array([1, 2, 3], dtype=np.float64)
a
Παρακάτω ορίζουμε τη συνάρτηση powersum(x, y, power)
. Παρατηρείστε στον ορισμό της συνάρτησης ότι η παράμετρος power
παίρνει την default τιμή 1. Αν ο χρήστης δε δώσει τιμή γι' αυτή την παράμετρο σε μια κλήση της συνάρτησης powersum
τότε στην εκτέλεση του κώδικα της συνάρτησης αυτή θα έχει την τιμή 1. Αυτό ακριβώς κάνουμε αμέσως μετά τον ορισμό της συνάρτησης.
Παρατηρείστε επίσης (επόμενη γραμμή) ότι στην python
μπορούμε να δώσουμε τις παραμέτρους μιας συνάρτησης με οποιαδήποτε σειρά θέλουμε, αρκεί να δώσουμε και το όνομά τους μαζί.
def powersum(x, y, power=1):
return x**power + y**power
print powersum(2, 3)
print powersum(power=2, x=2, y=3.0)
Στη δεύτερη γραμμή παρακάτω from numpy import array as ar
βλέπουμε ένα μηχανισμό της python
ώστε να χρησιμοποιούμε ότι όνομα θέλουμε για να καλέσουμε τη συνάρτηση array
(στην περίπτωση αυτή το όνομα ar
).
Ορίζουμε δύο μονοδιάστατα arrays a
και b
και τους δίνουμε τιμές (η συνάρτηση ones
της numpy
επιστρέφει ένα array που έχει παντού 1), υπολογίζουμε το κατά σημείο αθροισμά τους c
και το τυπώνουμε. Το ίδιο κάνουμε στις επόμενες γραμμές και για το κατά σημείο γινόμενο και διαίρεση των διανυσμάτων a
και b
.
Ορίζουμε έπειτα το διδιάστατο πίνακα A
με διαστάσεις \(2 \times 3\), τον τυπώνουμε και επίσης τυπώνουμε το shape
(μορφή) του πίνακα που είναι ένα tuple με τόσες θέσεις όσες η διάσταση του array (δύο στην περίπτωση αυτή). Βλέπουμε ότι το shape
είναι το tuple (2,3), πρόκειται δηλ. για ένα πίνακα με 2 γραμμές και 3 στήλες (διδιάστατος πίνακας). Έπειτα ορίζουμε το διάνυσμα (μονοδιάστατος πίνακας) b
με τιμή [1, 1, 1] και παρατηρούμε ότι το shape
του b
είναι (3,)
, πρόκειται δηλ. για ένα μονοδιάστατο πίνακα με 3 θέσεις.
Τέλος χρησιμοποιούμε τη συνάρτηση dot
της numpy
η οποία υλοποιεί ακριβώς τον πολλαπλασιασμό πινάκων. Πολλαπλασιάζουμε τον πίνακα A
με διαστάσεις \(2\times 3\) με το διάνυσμα b
και παίρνουμε το αποτέλεσμα στο διάνυσμα c
.
import numpy as np
from numpy import array as ar
a = ar([1, 2, 3, 4], float); print "a is", a
b = np.ones(4); print "b is", b
c = a+b
print "c=a+b is", c
c = a*b
print "c=a*b is", c
c = b/a
print "c=a/b is", c
A = ar([ [1, 2, 3], [5, 2, 1] ])
print "A is\n", A
print "The shape of A is", A.shape
b = np.ones(3)
print "b is", b
print "The shape of b is", b.shape
c = np.dot(A, b)
print "c=A*b is", c
print "The shape of c is", c.shape
Μέσα στη numpy
βρίσκονται υλοποιημένες πολλές συναρτήσεις που είναι φτιαγμένες ώστε να εφαρμόζονται στα στοιχεία ενός πίνακα, αν τους περάσουμε πίνακα ως παράμετρο. Παρακάτω βλέπουμε τη συνάρτηση sin
(ημίτονο) της numpy
να εφαρμόζεται σε όλα τα στοιχεία του διανύσματος x
, το οποίο δημιουργήσαμε προηγουμένως με την arange
περιέχει τα στοιχεία μιας αριθμητικής προόδου με πρώτο στοιχείο το 0, βήμα το 0.1 και μέχρι αυτά να είναι \(< 0\). Οι τιμές που τυπώνονται ως τιμές του array y
είναι τα ημίτονα των αριθμών που είναι στο array x
, με τη σειρά αυτή.
import numpy as np
import math
x = np.arange(0, 10, 0.1)
y = np.sin(x)
print y