Σημειωματάριο Πέμπτης 26 Μαρ. 2015

Συνέχεια εισαγωγής στη βιβλιοθήκη numpy

Σήμερα στο μάθημα πήραμε με τη σειρά και από την αρχή τις ασκήσεις πάνω στη numpy που εμφανίζονται στο
http://www.labri.fr/perso/nrougier/teaching/numpy.100/),

(με κάποιες παραλλαγές δικές μας) τις κάνουμε μια μια και εξηγούμε

Ορίζουμε ένα μονοδιάστατο πίνακα A κι ένα \(2\times 3\) διδιάστατο πίνακα B και τους τυπώνουμε.

In [2]:
import numpy as np

A = np.array([1, 2, 3])
print "A is:", A

B = np.array([ [1, 2, 3], [4, 5, 6] ])
print "B is:\n",B
A is: [1 2 3]
B is:
[[1 2 3]
 [4 5 6]]

Τυπώνουμε το string __version__ της numpy το οποίο μας λέει την έκδοση της βιβλιοθήκης που έχουμε εγκατεστημένη στον υπολογιστή μας. (Αφού το όνομα __version__ είναι δηλωμένο μέσα στη numpy, για την οποία έχουμε δηλώσει τη συντομογραφία np, θα πρέπει να γράψουμε np.__version__ για να βρει το όνομα η python.)

Έπειτα καλούμε τη συνάρτηση __config__.show() (για την ακρίβεια: η μέθοδος show του αντικειμένου __config__ το οποίο είναι δηλωμένο στη numpy) η οποία δίνει διάφορες πληροφορίες για την υλοποίηση της βιβλιοθήκης. Για παράδειγμα βλέπουμε ότι η έκδοση της βιβλιοθήκης BLAS (Basic Linear Algebra Subprograms) έχει υλοποιηθεί στη Fortran 77 (εξ ου και το f77).

In [4]:
import numpy as np

print np.__version__
np.__config__.show()
1.8.2
blas_info:
    libraries = ['blas']
    library_dirs = ['/usr/lib']
    language = f77
lapack_info:
    libraries = ['lapack']
    library_dirs = ['/usr/lib']
    language = f77
atlas_threads_info:
  NOT AVAILABLE
blas_opt_info:
    libraries = ['blas']
    library_dirs = ['/usr/lib']
    language = f77
    define_macros = [('NO_ATLAS_INFO', 1)]
atlas_blas_threads_info:
  NOT AVAILABLE
openblas_info:
  NOT AVAILABLE
lapack_opt_info:
    libraries = ['lapack', 'blas']
    library_dirs = ['/usr/lib']
    language = f77
    define_macros = [('NO_ATLAS_INFO', 1)]
atlas_info:
  NOT AVAILABLE
lapack_mkl_info:
  NOT AVAILABLE
blas_mkl_info:
  NOT AVAILABLE
atlas_blas_info:
  NOT AVAILABLE
mkl_info:
  NOT AVAILABLE

Δημιουργούμε ένα διάνυσμα Z με 10 μηδενικά καλώντας τη συνάρτηση zeros, και το τυπώνουμε.

In [9]:
import numpy as np

Z = np.zeros(10)
print Z
[ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]

Όπως και πριν αλλά τώρα επεμβαίνουμε στο διάνυσμα Z και του αλλάζουμε το πέμπτο στοιχείο του σε 1 με την εντολή Z[4]=1.

In [10]:
import numpy as np

Z = np.zeros(10)
Z[4] = 1
print Z
[ 0.  0.  0.  0.  1.  0.  0.  0.  0.  0.]

Χρησιμοποιούμε κατ' αρχήν την arange για να πάρουμε ένα μονοδιάστατο διάνυσμα που τα στοιχεία του αρχίζουν από το 10, αυξάνουν κατά 2 έως ότου είναι < 50. Αυτό είναι το αποτέλεσμα της np.arange(10,50,2) (20 στοιχεία).

Με τη μέθοδο reshape τώρα μπορούμε να πάρουμε τα στοιχεία ενός πίνακα και να τα αναδιατάξουμε σε νέα μορφή. Με το reshape((2,10)) (προσέξτε τις διπλές παρενθέσεις) αναδιατάσσουμε το προηγούμενο μονοδιάστατο διάνυσμα σε ένα διδιάστατο πίνακα \(2 \times 10\), τον αναθέτουμε στη μεταβλητή Z την οποία και τυπώνουμε.

In [18]:
import numpy as np

Z = np.arange(10,50,2).reshape((2,10))

print Z
[[10 12 14 16 18 20 22 24 26 28]
 [30 32 34 36 38 40 42 44 46 48]]

Στο επόμενο κάνουμε το ίδιο αλλά διατηρούμε τον αρχικό πίνακα (πριν το reshape) σε μια άλλη μεταβλητή και τυπώνουμε και τις δύο μεταβλητές.

In [6]:
import numpy as np

Z = np.arange(10,50,2)
W = Z.reshape((2,10))
print "Array before reshape is\n",Z,"\n"
print "Array after reshape is\n",W
Array before reshape is
[10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42 44 46 48] 

Array after reshape is
[[10 12 14 16 18 20 22 24 26 28]
 [30 32 34 36 38 40 42 44 46 48]]

Φτιάχνουμε ένα πίνακα F (μονοδιάστατο διάνυσμα) και με τη συνάρτηση nonzero παίρνουμε σε ένα array στη μεταβλητή nz τις θέσεις (στο F) των μη μηδενικών στοιχείων του F. Έχοντας υπολογίσει στο array με όνομα nz τις θέσεις αυτές, μπορούμε γράφοντας F[nz] να πάρουμε εκείνο το υποδιάνυσμα του F που περιέχει μόνο τα μη μηδενικά στοιχεία του.

In [27]:
import numpy as np

F = np.array([1,2,0,0,4,0])
nz = np.nonzero(F)
print nz
print F[nz]
(array([0, 1, 4]),)
[1 2 4]

Όπως πριν γράψαμε F[nz] (όπου nz είναι array) και πήραμε ουσιαστικά το F περιορισμένο στις θέσεις που λέει το nz έτσι κι εδώ μπορούμε να πάρουμε το διάνυσμα X και να το περιορίσουμε στις θέσεις του 6, 4, 3 (με αυτή τη σειρά) δίνοντας του τις θέσεις αυτές σε λίστα X[ [6, 4, 3] ].

In [42]:
import numpy as np

X = array([1, 1, 3, 3, 4, 5, 2, 7])
print X
print X[[6,4,3]]
[1 1 3 3 4 5 2 7]
[2 4 3]

Βλέπουμε κατ' αρχήν εδώ στη δεύτερη γραμμή το πώς η python μας επιτρέπει να ορίσουμε συντομογραφία για οτιδήποτε θέλουμε. Εδώ π.χ. ορίζουμε τη συντομογραφία ar για το όνομα np.array.

Φτιάχνουμε έπειτα τον \(3\times 4\) πίνακα F και τον τυπώνουμε.

Στην τελευταία γραμμή

print F[0:2, 0:2]

βλέπουμε το μηχανισμό του slicing ("φέτες") με τον οποίο μπορούμε να πάρουμε ένα υποπίνακα ενός πίνακα περιορίζοντας τις γραμμές του σε κάποιο υποσύνολο από αυτές και ομοίως και τις στήλες.

Το 0:2 εδώ σημαίνει τους αριθμούς 0,1 (περίπου σα να γράφαμε range(0,2)), οπότε ο πίνακας που παίρνουμε από τον F είναι αυτός που προκύπτει από τον F αν περιοριστούμε στις δύο πρώτες γραμμές και στήλες.

In [7]:
import numpy as np
ar = np.array

F = ar([ [1, 2, 3, 4], [5, 6, 7, 8], [0, 0, 0, 0] ])
print F

print F[0:2, 0:2]
[[1 2 3 4]
 [5 6 7 8]
 [0 0 0 0]]
[[1 2]
 [5 6]]

Εδώ βλέπουμε κι άλλα παραδείγματα slicing. Ξεκινούμε κατ' αρχήν από ένα διάνυσμα A που περιέχει τους αριθμούς 0 .. 24, το οποίο το κάνουμε reshape σε μορφή \(5 \times 5\), οπότε προκύπτει ο πίνακας A που τυπώνεται και πρώτος.

Στο 1: επιλέγουμε τις γραμμές 2 και 3 και τις στήλες 1 και 2 του αρχικού.

Στο 2: επιλέγουμε όλες τις γραμμές του αρχικού και τη στήλη 3 μόνο. Προσέξτε ότι ο πίνακας που τυπώνεται δεν είναι πίνακας διάνυσμα (μονοδιάστατος) αλλά διδιάστατος πίνακα με shape \(5\times 1\). Αυτό μπορεί να το λέμε διάνυσμα αλλά δεν είναι μονοδιάστατος πίνακας αλλά διδιάστατος όσο αφορά τη numpy.

Στο 3: δεν κάνουμε απολύτως κανένα περιορισμό στις γραμμές ή τις στήλες του A.

Στο 4: κρατάμε όλες τις γραμμές και όλες τις στήλες από την 3 και πέρα.

Στο 5: κρατάμε όλες τις γραμμές που είναι μικρότερες από το πλήθος των στηλών -2 και όλες τις στήλες που είναι μικρότερες από το πλήθος των στηλών -1. Με άλλα λόγια όταν σε ένα slice γράφουμε -1 εννούμε το συνολικό μήκος -1, δηλ. το δείκτη του τελευταίου στοιχείου. Ομοίως το -2 είναι ο δείκτης του προτελευταίου στοιχείου.

In [9]:
import numpy as np

A = np.arange(25).reshape(5,5)
print "Original array is\n",A

print "1:\n", A[2:4, 1:3]

print "2:\n", A[:, 3:4]

print "3:\n", A[:,:]

print "4:\n", A[3:, 3:]

print "5:\n", A[:-2, :-1]
Original array is
[[ 0  1  2  3  4]
 [ 5  6  7  8  9]
 [10 11 12 13 14]
 [15 16 17 18 19]
 [20 21 22 23 24]]
1:
[[11 12]
 [16 17]]
2:
[[ 3]
 [ 8]
 [13]
 [18]
 [23]]
3:
[[ 0  1  2  3  4]
 [ 5  6  7  8  9]
 [10 11 12 13 14]
 [15 16 17 18 19]
 [20 21 22 23 24]]
4:
[[18 19]
 [23 24]]
5:
[[ 0  1  2  3]
 [ 5  6  7  8]
 [10 11 12 13]]

Η συνάρτηση eye(N) παράγει τον \(N\times N\) ταυτοτικό πίνακα, τον πίνακα δηλ. μορφής \(N\times N\) με μηδενικά παντού εκτός από την διαγώνιο όπου έχει 1.

In [57]:
import numpy as np

Z = np.eye(3)
print Z
[[ 1.  0.  0.]
 [ 0.  1.  0.]
 [ 0.  0.  1.]]

Σκοπός σε αυτή την άσκηση είναι να φτιάξουμε ένα \(5\times 5\) πίνακα με τις τιμές 1, 2, 3, 4 (που τις βάζουμε στο διάνυσμα values) στην υποδιαγώνιο και τις ίδιες τιμές στην υπερδιαγώνιο και παντού αλλού 0.

Με τη συνάρτηση diag φτιάχνουμε ένα διδιάστατο πίνακα (η μορφή του υπολογίζεται αυτόματα από το μήκος της πρώτης παραμέτρου και την παράμετρο k) του οποίου η k-υπερδιαγώνιος έχει τις τιμές του πίνακα values. Για \(k=0\) έχουμε την κύρια διαγώνιο για \(k\) αρνητικό έχουμε υποδιαγωνίους και για \(k\) θετικό έχουμε υπερδιαγωνίους.

Έτσι καλούμε την diag δύο φορές. Την πρώτη φορά φτιάχνουμε ένα πίνακα που έχει μηδενικά παντού εκτός από την πρώτη υποδιαγώνιο (\(k=-1\)) και τη δεύτερη φορά φτιάχνουμε ένα πίνακα που έχει μηδενικά παντού εκτός από την πρώτη υπερδιαγώνιο (\(k=1\)). Προσθέτοντας αυτούς τους πίνακες παίρνουμε αυτό που θέλαμε να φτιάξουμε.

In [10]:
import numpy as np

values = 1+np.arange(4); print values

Z = np.diag(values,k=-1) + np.diag(values, k=+1)

print Z
[1 2 3 4]
[[0 1 0 0 0]
 [1 0 2 0 0]
 [0 2 0 3 0]
 [0 0 3 0 4]
 [0 0 0 4 0]]

Εδώ χρησιμοποιούμε ξανά τη diag για να φτιάξουμε ένα \(5\times 5\) πίνακα που έχει παντού 1 εκτός από την πρώτη υποδιαγώνιο όπου έχει 2.

Στην τρίτη γραμμή παρατηρείστε ότι το διάνυσμα d φτιάχνεται πολλαπλασιάζοντας ένα διάνυσμα που έχει όλα 1 με τον αριθμό 2.

Στην τελευταία γραμμή προσθέτουμε στον πίνακα A τον πίνακα που έχει μόνο στην υποδιαγώνιο τις τιμές του διανύσματος d αλλά αφού τους αφαιρέσουμε το 1, ώστε το άθροισμα να βγει 2 στην υποδιαγώνιο.

In [12]:
import numpy as np

A = np.ones((5,5)); print A

d = np.ones(4)*2; print d

print A+np.diag(d-1, k=-1)
[[ 1.  1.  1.  1.  1.]
 [ 1.  1.  1.  1.  1.]
 [ 1.  1.  1.  1.  1.]
 [ 1.  1.  1.  1.  1.]
 [ 1.  1.  1.  1.  1.]]
[ 2.  2.  2.  2.]
[[ 1.  1.  1.  1.  1.]
 [ 2.  1.  1.  1.  1.]
 [ 1.  2.  1.  1.  1.]
 [ 1.  1.  2.  1.  1.]
 [ 1.  1.  1.  2.  1.]]

Με τη συνάρτηση set_printoptions(precision=3) λέμε στη numpy όταν τυπώνει αριθμούς να τυπώνει μέχρι 3 δεκαδικά ψηφία.

Με την εντολή A = np.random.random((5,5)) καλούμε τη συνάρτηση random του υποmodule (της υποβιβλιοθήκης) random της βιβλιοθήκης numpy, και φτιάχνουμε ένα πίνακα με μορφή (5,5) του οποίου τα στοιχεία είναι τυχαία στο διάστημα [0,1] (με ομοιόμορφη κατανομή).

Αυτού του πίνακα την πρώτη υποδιαγώνιο θέλουμε να κάνουμε όλα 2 (όπως και πριν αλλά τώρα οι τιμές που υπάρχουν αυτή τη στιγμή στην πρώτη διαγώνιο δεν είναι γνωστές εκ των προτέρων και ούτε είναι όλες ίδιες).

Δημιουργούμε και πάλι το διάνυσμα d (που θα πάει στη θέση της 1ης υποδιαγωνίου) όπως και πριν.

Χρησιμοποιούμε και πάλι την diag για να πάρουμε από τον πίνακα A τις τιμές της 1ης υποδιαγωνίου του (η diag δουλεύει με δύο τρόπους ανάλογα με το αν της περάσουμε μονοδιάστατο ή διδιάστατο πίνακα δείτε το manual. Σε περίπτωση που της περάσουμε διδιάστατο πίνακα (όπως στον υπολογισμό της μεταβλητής dd) η diag δεν κατασκευάζει κανένα νέο πίνακα αλλά μας γυρνάει (ως ένα μονοδιάστατο array) την \(k\)-υπερδιαγώνιο του πίνακα που δώσαμε. Έτσι το διάνυσμα dd περιέχει την 1η υποδιαγώνιο του πίνακα A.

Φτιάχνουμε λοιπόν με την diag ένα πίνακα που έχει παντού μηδενικά εκτός από την 1η υποδιαγώνιο όπου έχει τις τιμές d-dd (επιθυμητές μείον υπάρχουσες) και τον πίνακα αυτό τον προσθέτουμε στον A για να πάρουμε αυτό που θέλουμε.

In [13]:
import numpy as np
np.set_printoptions(precision=3)

A = np.random.random((5,5)); print A

d = np.ones(4)*2

dd = np.diag(A, k=-1); print dd

print A+np.diag(d-dd, k=-1)
[[ 0.679  0.701  0.038  0.88   0.925]
 [ 0.266  0.695  0.797  0.684  0.279]
 [ 0.311  0.339  0.515  0.431  0.959]
 [ 0.725  0.602  0.392  0.565  0.561]
 [ 0.489  0.717  0.048  0.024  0.614]]
[ 0.266  0.339  0.392  0.024]
[[ 0.679  0.701  0.038  0.88   0.925]
 [ 2.     0.695  0.797  0.684  0.279]
 [ 0.311  2.     0.515  0.431  0.959]
 [ 0.725  0.602  2.     0.565  0.561]
 [ 0.489  0.717  0.048  2.     0.614]]

Εδώ βλέπουμε πώς μπορούμε να χρησιμοποιήσουμε slicing για να φτιάξουμε μια \(8 \times 8\) σκακιέρα, ένα πίνακα δηλ. στον οποίο τα 0 και 1 να εναλλάσσονται και στις δύο κατευθύνσεις.

Ξεκινάμε θέτοντας το Z να είναι ένας \(8 \times 8\) πίνακας από μηδενικά (τύπου ακέραιου αφού δε θα χρειαστούμε να κάνουμε πράξεις ακριβείας πάνω σε αυτά).

Ένα slice της μορφής start : stop : step μας λέει να ξεκινήσουμε από την τιμή start και να προχωράμε κάθε φορά κατά step, για όσο είμαστε αυστηρά μικρότεροι από το stop.

Έτσι στην εντολή Z[1::2,::2] = 1 το πρώτο slice (στις γραμμές) μας λέει να πάρουμε εκείνες τις γραμμές που είναι περιττές ενώ το δέυτερο εκείνες τις στήλες που είναι άρτιες και όλες αυτές οι θέσεις του πίνακα παίρνουν την ίδια τιμή, 1.

Στην εντολή Z[::2,1::2] = 1 θέτουμε ίσα με 1 όλα τα στοιχεία του πίνακα Z με άρτια γραμμή και περιττή στήλη.

In [72]:
import numpy as np

Z = np.zeros((8,8),dtype=int)

Z[1::2,::2] = 1
print Z
Z[::2,1::2] = 1
print Z
[[0 0 0 0 0 0 0 0]
 [1 0 1 0 1 0 1 0]
 [0 0 0 0 0 0 0 0]
 [1 0 1 0 1 0 1 0]
 [0 0 0 0 0 0 0 0]
 [1 0 1 0 1 0 1 0]
 [0 0 0 0 0 0 0 0]
 [1 0 1 0 1 0 1 0]]
[[0 1 0 1 0 1 0 1]
 [1 0 1 0 1 0 1 0]
 [0 1 0 1 0 1 0 1]
 [1 0 1 0 1 0 1 0]
 [0 1 0 1 0 1 0 1]
 [1 0 1 0 1 0 1 0]
 [0 1 0 1 0 1 0 1]
 [1 0 1 0 1 0 1 0]]

Εδώ πετυχαίνουμε το ίδιο αποτέλεσμα με τη συνάρτηση tile, η οποία παίρνει ένα array και το επαναλαμβάνει τόσες φορές όσο του λέει ηη δεύτερη παράμετρος που είναι ένα shape. Έτσι παίρνουμε ως επαναλαμβανόμενο array την πάνω αριστερά 2x2 γωνία του πίνακα που θέλουμε να φτιάξουμε και το επαναλαμβάνουμε σε μια επανάληψη 4x4.

In [14]:
import numpy as np

Z = np.tile( np.array([[0,1],[1,0]]), (4,4))
print Z
[[0 1 0 1 0 1 0 1]
 [1 0 1 0 1 0 1 0]
 [0 1 0 1 0 1 0 1]
 [1 0 1 0 1 0 1 0]
 [0 1 0 1 0 1 0 1]
 [1 0 1 0 1 0 1 0]
 [0 1 0 1 0 1 0 1]
 [1 0 1 0 1 0 1 0]]

Βλέπουμε ξανά εδώ πολλαπλασιασμό πινάκων.

In [82]:
import numpy as np

Z = np.dot(np.ones((5,3)), np.ones((3,2)))
print Z
[[ 3.  3.]
 [ 3.  3.]
 [ 3.  3.]
 [ 3.  3.]
 [ 3.  3.]]