Numpy

  • Numpy steht für Numerical Python und ist die Grundlage für wissenschaftliche Datenverarbeitung
  • Stellt viele algebraische Methoden zur Verfügung

Motivation:

  • Meist hat man nach in einer Auswertung Datenpunkte, die verarbeitet werden müssen
  • Numpy ist eine Python-Bibliothek, die den Umgang mit Datenpunkten enorm vereinfacht
In [1]:
%%javascript
$.getScript('https://kmahelona.github.io/ipython_notebook_goodies/ipython_notebook_toc.js')

Inhalt

Grundlagen

In [2]:
import numpy as np
  • Grunddatentyp von Numpy: das Array
  • Kann man sich als effizientere Liste vorstellen
  • Idee von Numpy: Man kann ein Array ähnlich wie eine Zahl verwenden. Operationen werden dann auf allen Elementen ausgeführt
  • Am besten versteht man das mit einigen Beispielen:
In [3]:
# convert list to array
x = np.array([1, 2, 3, 4, 5])
In [4]:
2 * x
Out[4]:
array([ 2,  4,  6,  8, 10])
In [5]:
x**2
Out[5]:
array([ 1,  4,  9, 16, 25])
In [6]:
x**x
Out[6]:
array([   1,    4,   27,  256, 3125])
In [7]:
np.cos(x)
Out[7]:
array([ 0.54030231, -0.41614684, -0.9899925 , -0.65364362,  0.28366219])

Achtung: Man brauch die cos Methode aus numpy!

In [8]:
import math
math.cos(x)
-------------------------------------------------------------------
TypeError                         Traceback (most recent call last)
<ipython-input-8-87cdf88e21ce> in <module>
      1 import math
----> 2 math.cos(x)

TypeError: only size-1 arrays can be converted to Python scalars

Bei großen Datensätzen ist die Laufzeit relevant:

In [9]:
%%timeit
xs = [42] * 10000
xs2 = [x**2 for x in xs]
1.62 ms ± 26.1 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
In [10]:
%%timeit 
x = np.full(10000, 42)
x2 = x**2
9.35 µs ± 148 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

Selbstgeschriebene Funktionen, die nur für eine Zahl geschrieben wurden, funktionieren oft ohne Änderung mit Arrays!

In [11]:
def poly(y):
    return y + 2 * y**2 - y**3

poly(x)
Out[11]:
array([  2,   2,  -6, -28, -70])
In [12]:
poly(np.pi)
Out[12]:
-8.125475224531307
In [13]:
# this also works:
def poly(x):
    return x + 2 * x**2 - x**3

poly(x)
Out[13]:
array([  2,   2,  -6, -28, -70])

Das erlaubt es einem unter anderem, sehr leicht physikalische Formeln auf seine Datenpunkte anzuwenden.

Arrays können beliebige Dimension haben:

In [14]:
# two-dimensional array
y = np.array([[1, 2, 3],
              [4, 5, 6],
              [7, 8, 9]])

y + y
Out[14]:
array([[ 2,  4,  6],
       [ 8, 10, 12],
       [14, 16, 18]])

Das erlaubt es z.B. eine ganze Tabelle als ein Array abzuspeichern.

Mit Arrays sind auch Matrixoperationen möglich:

In [15]:
A = np.array([[1,1],
              [0,1]])
B = np.array([[2,0],
              [3,4]])

# element-wise product
print(A * B)

# matrix product
print(A @ B)

# also with one-dimensional vectors
np.array([1, 2, 3]) @ np.array([4, 5, 6])
[[2 0]
 [0 4]]
[[5 4]
 [3 4]]
Out[15]:
32

Dimension von Arrays

In Numpy werden Dimensionen auch Achsen genannt.

In [16]:
a = np.array([1.5, 3.0, 4.2])
b = np.array([[1, 2], [3, 4]])

print(f'Array a \n a.ndim   {a.ndim} \n a.shape  {a.shape} \n a.size   {a.size} \n a.dtype  {a.dtype}')
print(f'Array b \n b.ndim   {b.ndim} \n b.shape  {b.shape} \n b.size   {b.size} \n b.dtype  {b.dtype}')
Array a 
 a.ndim   1 
 a.shape  (3,) 
 a.size   3 
 a.dtype  float64
Array b 
 b.ndim   2 
 b.shape  (2, 2) 
 b.size   4 
 b.dtype  int64

Erstellen von Arrays

Es gibt viele nützliche Funktionen, die bei der Erstellung von Arrays helfen:

In [17]:
np.zeros(10)
Out[17]:
array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])
In [18]:
np.ones((5, 2))
Out[18]:
array([[1., 1.],
       [1., 1.],
       [1., 1.],
       [1., 1.],
       [1., 1.]])
In [19]:
np.linspace(0, 1, 11)
Out[19]:
array([0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1. ])
In [20]:
# like range() for arrays:
np.arange(0, 10)
Out[20]:
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
In [21]:
np.logspace(-4, 5, 10)
Out[21]:
array([1.e-04, 1.e-03, 1.e-02, 1.e-01, 1.e+00, 1.e+01, 1.e+02, 1.e+03,
       1.e+04, 1.e+05])

Numpy Indexing

Numpy erlaubt einem sehr bequem bestimmte Elemente aus einem Array auszuwählen

In [22]:
x = np.arange(0, 10)
print(x)

# like lists:
x[4]
[0 1 2 3 4 5 6 7 8 9]
Out[22]:
4
In [23]:
# all elements with indices ≥1 and <4:
x[1:4]
Out[23]:
array([1, 2, 3])
In [24]:
# negative indices count from the end
x[-1], x[-2]
Out[24]:
(9, 8)
In [25]:
# combination:
x[3:-2]
Out[25]:
array([3, 4, 5, 6, 7])
In [26]:
# step size
x[::2]
Out[26]:
array([0, 2, 4, 6, 8])
In [27]:
# trick for reversal: negative step
x[::-1]
Out[27]:
array([9, 8, 7, 6, 5, 4, 3, 2, 1, 0])

Indexing1D

In [28]:
y = np.array([x, x + 10, x + 20, x + 30])
y
Out[28]:
array([[ 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, 25, 26, 27, 28, 29],
       [30, 31, 32, 33, 34, 35, 36, 37, 38, 39]])
In [29]:
# comma between indices
y[3, 2:-1]
Out[29]:
array([32, 33, 34, 35, 36, 37, 38])
In [30]:
# only one index ⇒ one-dimensional array
y[2]
Out[30]:
array([20, 21, 22, 23, 24, 25, 26, 27, 28, 29])
In [31]:
# other axis: (: alone means the whole axis)
y[:, 3]
Out[31]:
array([ 3, 13, 23, 33])
In [32]:
# inspecting the number of elements per axis:
y.shape
Out[32]:
(4, 10)

Indexing2D

Ausgewählten Elementen kann man auch direkt einen Wert zuweisen

In [33]:
y
Out[33]:
array([[ 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, 25, 26, 27, 28, 29],
       [30, 31, 32, 33, 34, 35, 36, 37, 38, 39]])
In [34]:
y[:, 3] = 0
y
Out[34]:
array([[ 0,  1,  2,  0,  4,  5,  6,  7,  8,  9],
       [10, 11, 12,  0, 14, 15, 16, 17, 18, 19],
       [20, 21, 22,  0, 24, 25, 26, 27, 28, 29],
       [30, 31, 32,  0, 34, 35, 36, 37, 38, 39]])

Man kann Indexing sogar gleichzeitig auf der linken und rechten Seite benutzen

In [35]:
y[:,0] = x[3:7]
y
Out[35]:
array([[ 3,  1,  2,  0,  4,  5,  6,  7,  8,  9],
       [ 4, 11, 12,  0, 14, 15, 16, 17, 18, 19],
       [ 5, 21, 22,  0, 24, 25, 26, 27, 28, 29],
       [ 6, 31, 32,  0, 34, 35, 36, 37, 38, 39]])

Transponieren des Arrays kehrt die Reihenfolge der Indizes um:

In [36]:
y
Out[36]:
array([[ 3,  1,  2,  0,  4,  5,  6,  7,  8,  9],
       [ 4, 11, 12,  0, 14, 15, 16, 17, 18, 19],
       [ 5, 21, 22,  0, 24, 25, 26, 27, 28, 29],
       [ 6, 31, 32,  0, 34, 35, 36, 37, 38, 39]])
In [37]:
y.shape
Out[37]:
(4, 10)
In [38]:
y.T
Out[38]:
array([[ 3,  4,  5,  6],
       [ 1, 11, 21, 31],
       [ 2, 12, 22, 32],
       [ 0,  0,  0,  0],
       [ 4, 14, 24, 34],
       [ 5, 15, 25, 35],
       [ 6, 16, 26, 36],
       [ 7, 17, 27, 37],
       [ 8, 18, 28, 38],
       [ 9, 19, 29, 39]])
In [39]:
y.T.shape
Out[39]:
(10, 4)

Masken

Oft will man Elemente auswählen, die eine bestimmte Bedingung erfüllen.

Hierzu erstellt man zuerst eine Maske (Arrays aus True/False-Werten).

Diese kann man in eckigen Klammern übergeben.

In [40]:
a = np.linspace(0, 2, 11)
b = a**2

print(a)

# create a mask for all elements >= 1
mask = a >= 1
print(mask)

print(a[mask])
# do it in one step:
print(a[a>=1])
[0.  0.2 0.4 0.6 0.8 1.  1.2 1.4 1.6 1.8 2. ]
[False False False False False  True  True  True  True  True  True]
[1.  1.2 1.4 1.6 1.8 2. ]
[1.  1.2 1.4 1.6 1.8 2. ]

Reduzieren von Arrays

Viele Rechenoperationen reduzieren ein Array auf einen einzelnen Wert

In [41]:
x
Out[41]:
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
In [42]:
y
Out[42]:
array([[ 3,  1,  2,  0,  4,  5,  6,  7,  8,  9],
       [ 4, 11, 12,  0, 14, 15, 16, 17, 18, 19],
       [ 5, 21, 22,  0, 24, 25, 26, 27, 28, 29],
       [ 6, 31, 32,  0, 34, 35, 36, 37, 38, 39]])

Summe aller Elemente

In [43]:
np.sum(x)
Out[43]:
45

Bei vielen Methoden kann die Dimension (Achse) mit angegeben werden

In [44]:
np.sum(y, axis=1)  # sum of each row
Out[44]:
array([ 45, 126, 207, 288])

Multiplikation aller Elemente

In [45]:
np.prod(x)
Out[45]:
0

Mittelwert der Einträge

In [46]:
np.mean(x)
Out[46]:
4.5

Standardabweichung der Einträge

In [47]:
np.std(x)
Out[47]:
2.8722813232690143

Fehler des Mittelwerts (geht auch einfacher):

In [48]:
np.std(x, ddof=1) / np.sqrt(len(x))
Out[48]:
0.9574271077563381

Schätzer der Standardabweichung

In [49]:
np.std(x, ddof=1)
Out[49]:
3.0276503540974917

Differenzen zwischen benachbarten Elementen

In [50]:
z = x**2
print('z ', z)

np.diff(z)
z  [ 0  1  4  9 16 25 36 49 64 81]
Out[50]:
array([ 1,  3,  5,  7,  9, 11, 13, 15, 17])

Input / Output

Einlesen aus Textdateien: genfromtxt

Sie gibt den Inhalt einer Textdatei als Array zurück.

Das Gegenstück ist savetxt.

In [51]:
n = np.arange(11)
x = np.linspace(0, 1, 11)

np.savetxt('test.txt', [n, x])
In [52]:
# see exercise 1-python/6-readwrite

with open('test.txt', 'r') as f:
    print(f.read())
0.000000000000000000e+00 1.000000000000000000e+00 2.000000000000000000e+00 3.000000000000000000e+00 4.000000000000000000e+00 5.000000000000000000e+00 6.000000000000000000e+00 7.000000000000000000e+00 8.000000000000000000e+00 9.000000000000000000e+00 1.000000000000000000e+01
0.000000000000000000e+00 1.000000000000000056e-01 2.000000000000000111e-01 3.000000000000000444e-01 4.000000000000000222e-01 5.000000000000000000e-01 6.000000000000000888e-01 7.000000000000000666e-01 8.000000000000000444e-01 9.000000000000000222e-01 1.000000000000000000e+00

In [53]:
data = np.array([n, x])

np.savetxt('test.txt', np.column_stack([n, x]))

with open('test.txt', 'r') as f:
    print(f.read())
0.000000000000000000e+00 0.000000000000000000e+00
1.000000000000000000e+00 1.000000000000000056e-01
2.000000000000000000e+00 2.000000000000000111e-01
3.000000000000000000e+00 3.000000000000000444e-01
4.000000000000000000e+00 4.000000000000000222e-01
5.000000000000000000e+00 5.000000000000000000e-01
6.000000000000000000e+00 6.000000000000000888e-01
7.000000000000000000e+00 7.000000000000000666e-01
8.000000000000000000e+00 8.000000000000000444e-01
9.000000000000000000e+00 9.000000000000000222e-01
1.000000000000000000e+01 1.000000000000000000e+00

Man sollte aber immer erklären, was man da abspeichert:

In [54]:
n = np.arange(11)
x = np.linspace(0, 1, 11)

# header schreibt eine Kommentarzeile in die erste Zeile der Datei
np.savetxt('test.txt', np.column_stack([n, x]), header="n x")
with open('test.txt', 'r') as f:
    print(f.read())
# n x
0.000000000000000000e+00 0.000000000000000000e+00
1.000000000000000000e+00 1.000000000000000056e-01
2.000000000000000000e+00 2.000000000000000111e-01
3.000000000000000000e+00 3.000000000000000444e-01
4.000000000000000000e+00 4.000000000000000222e-01
5.000000000000000000e+00 5.000000000000000000e-01
6.000000000000000000e+00 6.000000000000000888e-01
7.000000000000000000e+00 7.000000000000000666e-01
8.000000000000000000e+00 8.000000000000000444e-01
9.000000000000000000e+00 9.000000000000000222e-01
1.000000000000000000e+01 1.000000000000000000e+00

Einlesen der Werte mit genfromtxt :

In [55]:
a, b = np.genfromtxt('test.txt', unpack=True)
a, b
Out[55]:
(array([ 0.,  1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9., 10.]),
 array([0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1. ]))

Um die Datentypen zu erhalten, muss fmt angegeben werden:

In [56]:
np.savetxt(
    'test.txt',
    np.column_stack([n, x]),
    fmt=['%d', '%.4f'],       # first column integer, second 4 digits float
    delimiter=',',
    header='n,x',
)
In [57]:
data = np.genfromtxt(
    'test.txt',
    dtype=None,    # guess data types
    delimiter=',', 
    names=True,
)

data ist ein besonderes array, das sich ähnlich wie ein dict verhält:

In [58]:
data
Out[58]:
array([( 0, 0. ), ( 1, 0.1), ( 2, 0.2), ( 3, 0.3), ( 4, 0.4), ( 5, 0.5),
       ( 6, 0.6), ( 7, 0.7), ( 8, 0.8), ( 9, 0.9), (10, 1. )],
      dtype=[('n', '<i8'), ('x', '<f8')])
In [59]:
data['n'], data.shape, data.dtype
Out[59]:
(array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10]),
 (11,),
 dtype([('n', '<i8'), ('x', '<f8')]))
In [ ]: