הפקולטה לפיזיקה, הטכניון. חורף 2013
מרצה: רונן אברבנאל
כבר ראינו כמה פעולות פשוטות עם מערכים של numpy אבל בכל זאת, נרצה להדגיש את היתרונות שלהם על פני טיפוסי הנתונים הרגילים של פייתון:
import numpy as np
חד ממדי:
a = np.array([0,1,2,3])
a
array([0, 1, 2, 3])
a.ndim
1
a.shape
(4,)
len(a)
4
ממד גבוהה יותר:
b = np.array([[0,1,2],
[3,4,5]])
b
array([[0, 1, 2], [3, 4, 5]])
b.ndim
2
b.shape
(2, 3)
len(b)
2
מחזיר רק את הממד הראשון!
c = np.array( [[[1],[2]],[[3],[4]]])
c
array([[[1], [2]], [[3], [4]]])
c.shape
(2, 2, 1)
בדרך כלל, זה לא כל כך כיף להכניס מידע למערכים בזה אחר זה. אז יש פנקציות שעושות את זה:
a = np.arange(10)
a
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
b = np.arange(1,9,2)
b
array([1, 3, 5, 7])
c = np.linspace(0,1,6)
c
array([ 0. , 0.2, 0.4, 0.6, 0.8, 1. ])
d = np.linspace(0,1,5,endpoint=False)
d
array([ 0. , 0.2, 0.4, 0.6, 0.8])
a = np.ones((3,4))
a
array([[ 1., 1., 1., 1.], [ 1., 1., 1., 1.], [ 1., 1., 1., 1.]])
b = np.zeros((2,3))
b
array([[ 0., 0., 0.], [ 0., 0., 0.]])
c = np.eye(3)
c
array([[ 1., 0., 0.], [ 0., 1., 0.], [ 0., 0., 1.]])
d = np.diag([1,2,3,4])
d
array([[1, 0, 0, 0], [0, 2, 0, 0], [0, 0, 3, 0], [0, 0, 0, 4]])
a = np.random.rand(4) #uniform in [0,1]
a
array([ 0.35390507, 0.49263593, 0.91330145, 0.76582638])
b = np.random.randn(3,4) # Normal distribution
b
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.
a = np.array([1,2,3])
a.dtype
dtype('int64')
b = np.array([1.,2.,3.])
b.dtype
dtype('float64')
אפשר גם להגדיר מפורשות:
c = np.array([1,2,3,], dtype=float)
c.dtype
dtype('float64')
סוגים נוספים במתשמע:
d = np.array([1+2j, 3+4j, 5+6*1j])
d.dtype
dtype('complex128')
e = np.array([True, False, False, True])
e.dtype
dtype('bool')
f = np.array(['Bonjour', 'Hello', 'Hallo',])
f.dtype # <--- strings containing max. 7 letters
dtype('S7')
צורת הגישה לאיברים במערך של numpy שונה במעט מצורת הגישה לאיברים במערך (או: רשימה) פייתוני רגיל, בפרט, במערך רב ממדי, אם כי צורת הגישה הסטנדרטית עובדת גם היא.
a = np.diag(np.arange(3))
a
array([[0, 0, 0], [0, 1, 0], [0, 0, 2]])
a[1, 1]
1
a[2, 1] = 10 # third line, second column
a
array([[ 0, 0, 0], [ 0, 1, 0], [ 0, 10, 2]])
a[1]
array([0, 1, 0])
נשים לב שבדו ממד, הממד הראשון מתייחס לשורות והשני לעמודות.
פריסה של מערך, כמו עבור רשימות:
a = np.arange(10)
a
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
a[2:9:3] # [start:end:step]
array([2, 5, 8])
slicing
אם מציבים ערכים לתוך "חלק" ממערך, זה עובד יופי,
a
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
a[5:] = 10
a
array([ 0, 1, 2, 3, 4, 10, 10, 10, 10, 10])
b = np.arange(5)
a[5:] = b[::-1]
a
array([0, 1, 2, 3, 4, 4, 3, 2, 1, 0])
המושג המקצועי כאן הוא fancy indexing
מעבירים מערך אם אותו אורך כמו המערך המקורי, כך שערכי המערך הם True או False. מקבלים רק את האיברים מהמערך המקורי עבורם העברנו True.
np.random.seed(3)
a = np.random.random_integers(0,20,15)
a
array([10, 3, 8, 0, 19, 10, 11, 9, 10, 6, 0, 20, 12, 7, 14])
mask = (a%3 == 0)
mask
array([False, True, False, True, False, False, False, True, False, True, True, False, True, False, False], dtype=bool)
כלומר, קיבלנו מערך המתאים למערך המקורי, אבכל מקום שבו במערך המקורי יש מספר שמתחלק ב-3, במסכה יש True.
extract_from_a = a[mask]
extract_from_a
array([ 3, 0, 9, 6, 0, 12])
אפשר גם לעשות את זה ישירות, ואפילו להציב מספרים.
נציב \(-1\) בכל המספרים שמתחלקים ב-3
a[a%3 == 0] = -1
a
array([10, -1, 8, -1, 19, 10, 11, -1, 10, -1, -1, 20, -1, 7, 14])
np.random.seed(3)
b = np.random.random_integers(0, 20, (8,2))
b
array([[10, 3], [ 8, 0], [19, 10], [11, 9], [10, 6], [ 0, 20], [12, 7], [14, 17]])
b[b[:,0]>10,1] = -1
b
array([[10, 3], [ 8, 0], [19, -1], [11, -1], [10, 6], [ 0, 20], [12, -1], [14, -1]])
הצבנו 1 בעמודה השניה, בכל מקום בו העמודה הראשונה גדולה מ-10!
a = np.arange(0, 100, 10)
a
array([ 0, 10, 20, 30, 40, 50, 60, 70, 80, 90])
a[[2, 3, 2, 4, 2]] # note: [2, 3, 2, 4, 2] is a Python list
array([20, 30, 20, 40, 20])
ניתן להציב גם ערכים חדשים:
a[[9, 7]] = -100
a
array([ 0, 10, 20, 30, 40, 50, 60, -100, 80, -100])
ניתן לבצע פעולות עם סקאלרים
a = np.array([1, 2, 3, 4])
a + 1
array([2, 3, 4, 5])
a**2
array([ 1, 4, 9, 16])
ולבצע פעולות אריתמטיות בין מערכים (איבר-איבר)
b = np.ones(4) + 1
a - b
array([-1., 0., 1., 2.])
a * b
array([ 2., 4., 6., 8.])
j = np.arange(5)
2**(j + 1) - j
array([ 2, 3, 6, 13, 28])
במקרה זה, חשוב לשים לב שהמערכים הם מאותו גודל!
פעולות אלו מהירות בהרבה מאשר ביצוע פעולות איבר-איבר על מערך באמצעות לולאות.
חשוב להבדיל בין כפל-מטריצות שמתבצע באמצעות dot,
לבית כפל איבר-איבר
c = np.ones((3,3))
c*c
array([[ 1., 1., 1.], [ 1., 1., 1.], [ 1., 1., 1.]])
c.dot(c)
array([[ 3., 3., 3.], [ 3., 3., 3.], [ 3., 3., 3.]])
השוואות
a = np.array([1, 2, 3, 4])
b = np.array([4, 2, 2, 4])
a == b
array([False, True, False, True], dtype=bool)
a > b
array([False, False, True, False], dtype=bool)
פעולות לוגיות
a = np.array([1, 1, 0, 0], dtype=bool)
b = np.array([1, 0, 1, 0], dtype=bool)
np.logical_or(a, b)
array([ True, True, True, False], dtype=bool)
np.logical_and(a, b)
array([ True, False, False, False], dtype=bool)
פונקציות שונות
a = np.arange(10)
np.sin(a)
array([ 0. , 0.84147098, 0.90929743, 0.14112001, -0.7568025 , -0.95892427, -0.2794155 , 0.6569866 , 0.98935825, 0.41211849])
np.log(a)
array([ -inf, 0. , 0.69314718, 1.09861229, 1.38629436, 1.60943791, 1.79175947, 1.94591015, 2.07944154, 2.19722458])
np.exp(a)
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])
ושוב, הגודל חייב להיות תואם:
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)
שחלוף:
a = np.triu(np.ones((3, 3)), 1)
a
array([[ 0., 1., 1.], [ 0., 0., 1.], [ 0., 0., 0.]])
a.T
array([[ 0., 0., 0.], [ 1., 0., 0.], [ 1., 1., 0.]])
x = np.array([1, 2, 3, 4])
np.sum(x)
10
x.sum()
10
במערכים רב ממדיים, ניתן לצמצם לפי ציר ספציפי,
x = np.array([[1, 1], [2, 2]])
x
array([[1, 1], [2, 2]])
x.sum(axis=0) # columns (first dimension)
array([3, 3])
x[:, 0].sum(), x[:, 1].sum()
(3, 3)
x.sum(axis=1) # rows (second dimension)
array([2, 4])
x[0, :].sum(), x[1, :].sum()
(2, 4)
אבל גם,
y = np.array([[1, 2, 3], [5, 6, 1]])
y.mean(0)
array([ 3., 4., 2.])
y.std(0)
array([ 2., 2., 1.])
y.min()
1
y.min(0)
array([1, 2, 1])
ועוד
שיטוח
a = np.array([[1, 2, 3], [4, 5, 6]])
a.ravel()
array([1, 2, 3, 4, 5, 6])
a.T
array([[1, 4], [2, 5], [3, 6]])
a.T.ravel()
array([1, 4, 2, 5, 3, 6])
שינוי צורה
a.shape
(2, 3)
b = a.ravel()
b = b.reshape((2, 3))
b
array([[1, 2, 3], [4, 5, 6]])
a.reshape((2, -1)) # unspecified (-1) value is inferred
array([[1, 2, 3], [4, 5, 6]])
שינוי גודל
a = np.arange(4)
a.resize((8,))
a
array([0, 1, 2, 3, 0, 0, 0, 0])
אבל אם המערך הוא הפניה למערך אחר, זה לא יעבוד:
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