סדנאת פייתון לפיזיקאים

Numpy, חבילת היסוד של פייתון לשימוש מדעי

הפקולטה לפיזיקה, הטכניון. חורף 2013

מרצה: רונן אברבנאל

כבר ראינו כמה פעולות פשוטות עם מערכים של numpy אבל בכל זאת, נרצה להדגיש את היתרונות שלהם על פני טיפוסי הנתונים הרגילים של פייתון:

In [89]:
import numpy as np

יצירת מערך מרשימה

חד ממדי:

In [90]:
a = np.array([0,1,2,3])
a
Out[90]:
array([0, 1, 2, 3])
In [91]:
a.ndim
Out[91]:
1
In [92]:
a.shape
Out[92]:
(4,)
In [93]:
len(a)
Out[93]:
4

ממד גבוהה יותר:

In [94]:
b = np.array([[0,1,2],
              [3,4,5]])
b
Out[94]:
array([[0, 1, 2],
       [3, 4, 5]])
In [95]:
b.ndim
Out[95]:
2
In [96]:
b.shape
Out[96]:
(2, 3)
In [97]:
len(b) 
Out[97]:
2

מחזיר רק את הממד הראשון!

In [98]:
c = np.array( [[[1],[2]],[[3],[4]]])
In [99]:
c
Out[99]:
array([[[1],
        [2]],

       [[3],
        [4]]])
In [100]:
c.shape
Out[100]:
(2, 2, 1)

פונקציות ליצירת מערך

בדרך כלל, זה לא כל כך כיף להכניס מידע למערכים בזה אחר זה. אז יש פנקציות שעושות את זה:

In [101]:
a = np.arange(10) 
a
Out[101]:
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
In [102]:
b = np.arange(1,9,2)
b
Out[102]:
array([1, 3, 5, 7])
In [103]:
c = np.linspace(0,1,6)
c
Out[103]:
array([ 0. ,  0.2,  0.4,  0.6,  0.8,  1. ])
In [104]:
d = np.linspace(0,1,5,endpoint=False)
d
Out[104]:
array([ 0. ,  0.2,  0.4,  0.6,  0.8])
In [105]:
a = np.ones((3,4))
a
Out[105]:
array([[ 1.,  1.,  1.,  1.],
       [ 1.,  1.,  1.,  1.],
       [ 1.,  1.,  1.,  1.]])
In [106]:
b = np.zeros((2,3))
b
Out[106]:
array([[ 0.,  0.,  0.],
       [ 0.,  0.,  0.]])
In [107]:
c = np.eye(3)
c
Out[107]:
array([[ 1.,  0.,  0.],
       [ 0.,  1.,  0.],
       [ 0.,  0.,  1.]])
In [108]:
d = np.diag([1,2,3,4])
d
Out[108]:
array([[1, 0, 0, 0],
       [0, 2, 0, 0],
       [0, 0, 3, 0],
       [0, 0, 0, 4]])
In [109]:
a = np.random.rand(4) #uniform in [0,1]
a
Out[109]:
array([ 0.35390507,  0.49263593,  0.91330145,  0.76582638])
In [110]:
b = np.random.randn(3,4) # Normal distribution
b
Out[110]:
array([[-0.07372419,  0.35763584, -1.84356472,  0.5024211 ],
       [-1.59379481,  0.61335757,  1.58514359, -0.27925073],
       [-0.57478932,  0.75299169,  0.31640691,  0.56939549]])

טיפוסי נתונים

בדרך כלל, טיפוסי הנתונים של פייתון הם משתמעים. כאשר עושים נומריקה, לפעמים נחמד להגדיר אותם במפורש.

עבור מעיף פייתוני, מוגדר "טיפוס הנתונים של המערך", dtype.

In [111]:
a = np.array([1,2,3])
a.dtype
Out[111]:
dtype('int64')
In [112]:
b = np.array([1.,2.,3.])
b.dtype
Out[112]:
dtype('float64')

אפשר גם להגדיר מפורשות:

In [113]:
c = np.array([1,2,3,], dtype=float)
c.dtype
Out[113]:
dtype('float64')

סוגים נוספים במתשמע:

In [114]:
d = np.array([1+2j, 3+4j, 5+6*1j])
d.dtype
Out[114]:
dtype('complex128')
In [115]:
e = np.array([True, False, False, True])
e.dtype
Out[115]:
dtype('bool')
In [116]:
f = np.array(['Bonjour', 'Hello', 'Hallo',])
f.dtype     # <--- strings containing max. 7 letters
Out[116]:
dtype('S7')

אינדוקס וחיתוך

צורת הגישה לאיברים במערך של numpy שונה במעט מצורת הגישה לאיברים במערך (או: רשימה) פייתוני רגיל, בפרט, במערך רב ממדי, אם כי צורת הגישה הסטנדרטית עובדת גם היא.

In [117]:
a = np.diag(np.arange(3))
a
Out[117]:
array([[0, 0, 0],
       [0, 1, 0],
       [0, 0, 2]])
In [118]:
a[1, 1]
Out[118]:
1
In [119]:
a[2, 1] = 10 # third line, second column
a
Out[119]:
array([[ 0,  0,  0],
       [ 0,  1,  0],
       [ 0, 10,  2]])
In [120]:
a[1]
Out[120]:
array([0, 1, 0])

נשים לב שבדו ממד, הממד הראשון מתייחס לשורות והשני לעמודות.

פריסה של מערך, כמו עבור רשימות:

In [121]:
a = np.arange(10)
a
Out[121]:
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
In [122]:
a[2:9:3] # [start:end:step]
Out[122]:
array([2, 5, 8])
slicing

slicing

אם מציבים ערכים לתוך "חלק" ממערך, זה עובד יופי,

In [123]:
a
Out[123]:
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
In [124]:
a[5:] = 10
a
Out[124]:
array([ 0,  1,  2,  3,  4, 10, 10, 10, 10, 10])
In [125]:
b = np.arange(5)
a[5:] = b[::-1]
a
Out[125]:
array([0, 1, 2, 3, 4, 4, 3, 2, 1, 0])

אינדוקס מתחכם יותר

המושג המקצועי כאן הוא fancy indexing

אינדוקס באמצעות "מסכה בוליאנית"

מעבירים מערך אם אותו אורך כמו המערך המקורי, כך שערכי המערך הם True או False. מקבלים רק את האיברים מהמערך המקורי עבורם העברנו True.

In [126]:
np.random.seed(3)
a = np.random.random_integers(0,20,15)
a
Out[126]:
array([10,  3,  8,  0, 19, 10, 11,  9, 10,  6,  0, 20, 12,  7, 14])
In [127]:
mask = (a%3 == 0)
mask
Out[127]:
array([False,  True, False,  True, False, False, False,  True, False,
        True,  True, False,  True, False, False], dtype=bool)

כלומר, קיבלנו מערך המתאים למערך המקורי, אבכל מקום שבו במערך המקורי יש מספר שמתחלק ב-3, במסכה יש True.

In [128]:
extract_from_a = a[mask]
extract_from_a
Out[128]:
array([ 3,  0,  9,  6,  0, 12])

אפשר גם לעשות את זה ישירות, ואפילו להציב מספרים.

נציב \(-1\) בכל המספרים שמתחלקים ב-3

In [129]:
a[a%3 == 0] = -1
a
Out[129]:
array([10, -1,  8, -1, 19, 10, 11, -1, 10, -1, -1, 20, -1,  7, 14])
In [130]:
np.random.seed(3) 
b = np.random.random_integers(0, 20, (8,2))
b
Out[130]:
array([[10,  3],
       [ 8,  0],
       [19, 10],
       [11,  9],
       [10,  6],
       [ 0, 20],
       [12,  7],
       [14, 17]])
In [131]:
b[b[:,0]>10,1] = -1
b
Out[131]:
array([[10,  3],
       [ 8,  0],
       [19, -1],
       [11, -1],
       [10,  6],
       [ 0, 20],
       [12, -1],
       [14, -1]])

הצבנו 1 בעמודה השניה, בכל מקום בו העמודה הראשונה גדולה מ-10!

אינדוקס באמצעות מערך של שלמים

In [132]:
a = np.arange(0, 100, 10)
a
Out[132]:
array([ 0, 10, 20, 30, 40, 50, 60, 70, 80, 90])
In [133]:
a[[2, 3, 2, 4, 2]]  # note: [2, 3, 2, 4, 2] is a Python list
Out[133]:
array([20, 30, 20, 40, 20])

ניתן להציב גם ערכים חדשים:

In [134]:
a[[9, 7]] = -100
a
Out[134]:
array([   0,   10,   20,   30,   40,   50,   60, -100,   80, -100])

פעולות על אברי המערך

ניתן לבצע פעולות עם סקאלרים

In [135]:
a = np.array([1, 2, 3, 4])
a + 1
Out[135]:
array([2, 3, 4, 5])
In [136]:
a**2
Out[136]:
array([ 1,  4,  9, 16])

ולבצע פעולות אריתמטיות בין מערכים (איבר-איבר)

In [137]:
b = np.ones(4) + 1
a - b
Out[137]:
array([-1.,  0.,  1.,  2.])
In [138]:
a * b
Out[138]:
array([ 2.,  4.,  6.,  8.])
In [139]:
j = np.arange(5)
2**(j + 1) - j
Out[139]:
array([ 2,  3,  6, 13, 28])

במקרה זה, חשוב לשים לב שהמערכים הם מאותו גודל!

פעולות אלו מהירות בהרבה מאשר ביצוע פעולות איבר-איבר על מערך באמצעות לולאות.

כפל

חשוב להבדיל בין כפל-מטריצות שמתבצע באמצעות dot,

לבית כפל איבר-איבר

In [140]:
c = np.ones((3,3))
c*c
Out[140]:
array([[ 1.,  1.,  1.],
       [ 1.,  1.,  1.],
       [ 1.,  1.,  1.]])
In [141]:
c.dot(c)
Out[141]:
array([[ 3.,  3.,  3.],
       [ 3.,  3.,  3.],
       [ 3.,  3.,  3.]])

עוד דוגמאות

השוואות

In [142]:
a = np.array([1, 2, 3, 4])
b = np.array([4, 2, 2, 4])
a == b
Out[142]:
array([False,  True, False,  True], dtype=bool)
In [143]:
a > b
Out[143]:
array([False, False,  True, False], dtype=bool)

פעולות לוגיות

In [144]:
a = np.array([1, 1, 0, 0], dtype=bool)
b = np.array([1, 0, 1, 0], dtype=bool)
np.logical_or(a, b)
Out[144]:
array([ True,  True,  True, False], dtype=bool)
In [145]:
np.logical_and(a, b)
Out[145]:
array([ True, False, False, False], dtype=bool)

פונקציות שונות

In [146]:
a = np.arange(10)
np.sin(a)
Out[146]:
array([ 0.        ,  0.84147098,  0.90929743,  0.14112001, -0.7568025 ,
       -0.95892427, -0.2794155 ,  0.6569866 ,  0.98935825,  0.41211849])
In [147]:
np.log(a)
Out[147]:
array([       -inf,  0.        ,  0.69314718,  1.09861229,  1.38629436,
        1.60943791,  1.79175947,  1.94591015,  2.07944154,  2.19722458])
In [148]:
np.exp(a)
Out[148]:
array([  1.00000000e+00,   2.71828183e+00,   7.38905610e+00,
         2.00855369e+01,   5.45981500e+01,   1.48413159e+02,
         4.03428793e+02,   1.09663316e+03,   2.98095799e+03,
         8.10308393e+03])

ושוב, הגודל חייב להיות תואם:

In [149]:
a = np.arange(4)
a + np.array([1, 2])  
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-149-82c1c1d5b8c1> in <module>()
      1 a = np.arange(4)
----> 2 a + np.array([1, 2])

ValueError: operands could not be broadcast together with shapes (4) (2) 

שחלוף:

In [150]:
a = np.triu(np.ones((3, 3)), 1)
a
Out[150]:
array([[ 0.,  1.,  1.],
       [ 0.,  0.,  1.],
       [ 0.,  0.,  0.]])
In [151]:
a.T
Out[151]:
array([[ 0.,  0.,  0.],
       [ 1.,  0.,  0.],
       [ 1.,  1.,  0.]])

צמצום רשימות

In [152]:
x = np.array([1, 2, 3, 4])
np.sum(x)
Out[152]:
10
In [153]:
x.sum()
Out[153]:
10

במערכים רב ממדיים, ניתן לצמצם לפי ציר ספציפי,

In [154]:
x = np.array([[1, 1], [2, 2]])
x
Out[154]:
array([[1, 1],
       [2, 2]])
In [155]:
x.sum(axis=0)   # columns (first dimension)
Out[155]:
array([3, 3])
In [156]:
x[:, 0].sum(), x[:, 1].sum()
Out[156]:
(3, 3)
In [157]:
x.sum(axis=1)   # rows (second dimension)
Out[157]:
array([2, 4])
In [158]:
x[0, :].sum(), x[1, :].sum()
Out[158]:
(2, 4)

אבל גם,

In [159]:
y = np.array([[1, 2, 3], [5, 6, 1]])
y.mean(0)
Out[159]:
array([ 3.,  4.,  2.])
In [160]:
y.std(0)
Out[160]:
array([ 2.,  2.,  1.])
In [161]:
y.min()
Out[161]:
1
In [162]:
y.min(0)
Out[162]:
array([1, 2, 1])

ועוד

מניפולציה על הצורה של מערך

שיטוח

In [163]:
a = np.array([[1, 2, 3], [4, 5, 6]])
a.ravel()
Out[163]:
array([1, 2, 3, 4, 5, 6])
In [164]:
a.T
Out[164]:
array([[1, 4],
       [2, 5],
       [3, 6]])
In [165]:
a.T.ravel()
Out[165]:
array([1, 4, 2, 5, 3, 6])

שינוי צורה

In [166]:
a.shape
Out[166]:
(2, 3)
In [167]:
b = a.ravel()
b = b.reshape((2, 3))
b
Out[167]:
array([[1, 2, 3],
       [4, 5, 6]])
In [168]:
a.reshape((2, -1))    # unspecified (-1) value is inferred
Out[168]:
array([[1, 2, 3],
       [4, 5, 6]])

שינוי גודל

In [171]:
a = np.arange(4)
a.resize((8,))
a
Out[171]:
array([0, 1, 2, 3, 0, 0, 0, 0])

אבל אם המערך הוא הפניה למערך אחר, זה לא יעבוד:

In [170]:
b = a
a.resize((4,))   
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-170-fa61239db0dc> in <module>()
      1 b = a
----> 2 a.resize((4,))

ValueError: cannot resize an array references or is referenced
by another array in this way.  Use the resize function

תרגילון

  1. צרו, ללא הצבה מפורשת ,את המערך [[1, 6, 11], [2, 7, 12], [3, 8, 13], [4, 9, 14], [5, 10, 15]]
  2. הציגו את השורה השניה והרביעית
  3. חשבו את הממוצע של כל טור ושל כל שורה במערך.