Кафедра дискретной математики МФТИ

Курс математической статистики

Никита Волков

На основе http://www.inp.nsk.su/~grozin/python/

Библиотека numpy

Пакет numpy предоставляет $n$-мерные однородные массивы (все элементы одного типа); в них нельзя вставить или удалить элемент в произвольном месте. В numpy реализовано много операций над массивами в целом. Если задачу можно решить, произведя некоторую последовательность операций над массивами, то это будет столь же эффективно, как в C или matlab - львиная доля времени тратится в библиотечных функциях, написанных на C.

Одномерные массивы


In [1]:
import numpy as np

Можно преобразовать список в массив.


In [2]:
a = np.array([0, 2, 1])
a, type(a)


Out[2]:
(array([0, 2, 1]), numpy.ndarray)

print печатает массивы в удобной форме.


In [3]:
print(a)


[0 2 1]

Класс ndarray имеет много методов.


In [4]:
set(dir(a)) - set(dir(object))


Out[4]:
{'T',
 '__abs__',
 '__add__',
 '__and__',
 '__array__',
 '__array_finalize__',
 '__array_interface__',
 '__array_prepare__',
 '__array_priority__',
 '__array_struct__',
 '__array_wrap__',
 '__bool__',
 '__complex__',
 '__contains__',
 '__copy__',
 '__deepcopy__',
 '__delitem__',
 '__divmod__',
 '__float__',
 '__floordiv__',
 '__getitem__',
 '__iadd__',
 '__iand__',
 '__ifloordiv__',
 '__ilshift__',
 '__imatmul__',
 '__imod__',
 '__imul__',
 '__index__',
 '__int__',
 '__invert__',
 '__ior__',
 '__ipow__',
 '__irshift__',
 '__isub__',
 '__iter__',
 '__itruediv__',
 '__ixor__',
 '__len__',
 '__lshift__',
 '__matmul__',
 '__mod__',
 '__mul__',
 '__neg__',
 '__or__',
 '__pos__',
 '__pow__',
 '__radd__',
 '__rand__',
 '__rdivmod__',
 '__rfloordiv__',
 '__rlshift__',
 '__rmatmul__',
 '__rmod__',
 '__rmul__',
 '__ror__',
 '__rpow__',
 '__rrshift__',
 '__rshift__',
 '__rsub__',
 '__rtruediv__',
 '__rxor__',
 '__setitem__',
 '__setstate__',
 '__sub__',
 '__truediv__',
 '__xor__',
 'all',
 'any',
 'argmax',
 'argmin',
 'argpartition',
 'argsort',
 'astype',
 'base',
 'byteswap',
 'choose',
 'clip',
 'compress',
 'conj',
 'conjugate',
 'copy',
 'ctypes',
 'cumprod',
 'cumsum',
 'data',
 'diagonal',
 'dot',
 'dtype',
 'dump',
 'dumps',
 'fill',
 'flags',
 'flat',
 'flatten',
 'getfield',
 'imag',
 'item',
 'itemset',
 'itemsize',
 'max',
 'mean',
 'min',
 'nbytes',
 'ndim',
 'newbyteorder',
 'nonzero',
 'partition',
 'prod',
 'ptp',
 'put',
 'ravel',
 'real',
 'repeat',
 'reshape',
 'resize',
 'round',
 'searchsorted',
 'setfield',
 'setflags',
 'shape',
 'size',
 'sort',
 'squeeze',
 'std',
 'strides',
 'sum',
 'swapaxes',
 'take',
 'tobytes',
 'tofile',
 'tolist',
 'tostring',
 'trace',
 'transpose',
 'var',
 'view'}

Наш массив одномерный.


In [5]:
a.ndim


Out[5]:
1

В $n$-мерном случае возвращается кортеж размеров по каждой координате.


In [6]:
a.shape


Out[6]:
(3,)

size - это полное число элементов в массиве; len - размер по первой координате (в 1-мерном случае это то же самое).


In [7]:
len(a), a.size


Out[7]:
(3, 3)

numpy предоставляет несколько типов для целых (int16, int32, int64) и чисел с плавающей точкой (float32, float64).


In [8]:
a.dtype, a.dtype.name, a.itemsize


Out[8]:
(dtype('int64'), 'int64', 8)

Индексировать массив можно обычным образом.


In [9]:
a[1]


Out[9]:
2

Массивы - изменяемые объекты.


In [10]:
a[1] = 3
print(a)


[0 3 1]

Массивы, разумеется, можно использовать в for циклах. Но при этом теряется главное преимущество numpy - быстродействие. Всегда, когда это возможно, лучше использовать операции над массивами как едиными целыми.


In [11]:
for i in a:
    print(i)


0
3
1

Массив чисел с плавающей точкой.


In [12]:
b = np.array([0., 2, 1])
b.dtype


Out[12]:
dtype('float64')

Точно такой же массив.


In [13]:
c = np.array([0, 2, 1], dtype=np.float64)
print(c)


[ 0.  2.  1.]

Преобразование данных


In [14]:
print(c.dtype)
print(c.astype(int))
print(c.astype(str))


float64
[0 2 1]
['0.0' '2.0' '1.0']

Массив, значения которого вычисляются функцией. Функции передаётся массив. Так что в ней можно использовать только такие операции, которые применимы к массивам.


In [15]:
def f(i):
    print(i)
    return i ** 2

a = np.fromfunction(f, (5,), dtype=np.int64)
print(a)


[0 1 2 3 4]
[ 0  1  4  9 16]

In [16]:
a = np.fromfunction(f, (5,), dtype=np.float64)
print(a)


[ 0.  1.  2.  3.  4.]
[  0.   1.   4.   9.  16.]

Массивы, заполненные нулями или единицами. Часто лучше сначала создать такой массив, а потом присваивать значения его элементам.


In [17]:
a = np.zeros(3)
print(a)


[ 0.  0.  0.]

In [18]:
b = np.ones(3, dtype=np.int64)
print(b)


[1 1 1]

Если нужно создать массив, заполненный нулями, длины другого массива, то можно использовать конструкцию


In [19]:
np.zeros_like(b)


Out[19]:
array([0, 0, 0])

Функция arange подобна range. Аргументы могут быть с плавающей точкой. Следует избегать ситуаций, когда $(конец-начало)/шаг$ - целое число, потому что в этом случае включение последнего элемента зависит от ошибок округления. Лучше, чтобы конец диапазона был где-то посредине шага.


In [20]:
a = np.arange(0, 9, 2)
print(a)


[0 2 4 6 8]

In [21]:
b = np.arange(0., 9, 2)
print(b)


[ 0.  2.  4.  6.  8.]

Последовательности чисел с постоянным шагом можно также создавать функцией linspace. Начало и конец диапазона включаются; последний аргумент - число точек.


In [22]:
a = np.linspace(0, 8, 5)
print(a)


[ 0.  2.  4.  6.  8.]

Последовательность чисел с постоянным шагом по логарифмической шкале от $10^0$ до $10^1$.


In [23]:
b = np.logspace(0, 1, 5)
print(b)


[  1.           1.77827941   3.16227766   5.62341325  10.        ]

Массив случайных чисел.


In [24]:
print(np.random.random(5))


[ 0.17754706  0.13481988  0.85711884  0.18696899  0.55900193]

Случайные числа с нормальным (гауссовым) распределением (среднее 0, среднеквадратичное отклонение 1).


In [25]:
print(np.random.normal(size=5))


[-1.51473227  1.0408142   3.07774644 -0.67956312  0.20781344]

Операции над одномерными массивами

Арифметические операции проводятся поэлементно.


In [26]:
print(a + b)


[  1.           3.77827941   7.16227766  11.62341325  18.        ]

In [27]:
print(a - b)


[-1.          0.22172059  0.83772234  0.37658675 -2.        ]

In [28]:
print(a * b)


[  0.           3.55655882  12.64911064  33.74047951  80.        ]

In [29]:
print(a / b)


[ 0.          1.12468265  1.26491106  1.06696765  0.8       ]

In [30]:
print(a ** 2)


[  0.   4.  16.  36.  64.]

Когда операнды разных типов, они пиводятся к большему типу.


In [31]:
i = np.ones(5, dtype=np.int64)
print(a + i)


[ 1.  3.  5.  7.  9.]

numpy содержит элементарные функции, которые тоже применяются к массивам поэлементно. Они называются универсальными функциями (ufunc).


In [32]:
np.sin, type(np.sin)


Out[32]:
(<ufunc 'sin'>, numpy.ufunc)

In [33]:
print(np.sin(a))


[ 0.          0.90929743 -0.7568025  -0.2794155   0.98935825]

Один из операндов может быть скаляром, а не массивом.


In [34]:
print(a + 1)


[ 1.  3.  5.  7.  9.]

In [35]:
print(2 * a)


[  0.   4.   8.  12.  16.]

Сравнения дают булевы массивы.


In [36]:
print(a > b)


[False  True  True  True False]

In [37]:
print(a == b)


[False False False False False]

In [38]:
c = a > 5
print(c)


[False False False  True  True]

Кванторы "существует" и "для всех".


In [39]:
np.any(c), np.all(c)


Out[39]:
(True, False)

Модификация на месте.


In [40]:
a += 1
print(a)


[ 1.  3.  5.  7.  9.]

In [41]:
b *= 2
print(b)


[  2.           3.55655882   6.32455532  11.2468265   20.        ]

In [42]:
b /= a
print(b)


[ 2.          1.18551961  1.26491106  1.6066895   2.22222222]

При выполнении операций над массивами деление на 0 не возбуждает исключения, а даёт значения np.nan или np.inf.


In [43]:
print(np.array([0.0, 0.0, 1.0, -1.0]) / np.array([1.0, 0.0, 0.0, 0.0]))


[  0.  nan  inf -inf]
/usr/local/lib/python3.5/dist-packages/ipykernel/__main__.py:1: RuntimeWarning: divide by zero encountered in true_divide
  if __name__ == '__main__':
/usr/local/lib/python3.5/dist-packages/ipykernel/__main__.py:1: RuntimeWarning: invalid value encountered in true_divide
  if __name__ == '__main__':

In [44]:
np.nan + 1, np.inf + 1, np.inf * 0, 1. / np.inf


Out[44]:
(nan, inf, nan, 0.0)

Сумма и произведение всех элементов массива; максимальный и минимальный элемент; среднее и среднеквадратичное отклонение.


In [45]:
b.sum(), b.prod(), b.max(), b.min(), b.mean(), b.std()


Out[45]:
(8.2793423935260435,
 10.708241812210389,
 2.2222222222222223,
 1.1855196066926152,
 1.6558684787052087,
 0.40390033426607452)

In [46]:
x = np.random.normal(size=1000)
x.mean(), x.std()


Out[46]:
(0.0044615752810054305, 1.0268498160774624)

Имеются встроенные функции


In [47]:
print(np.sqrt(b))
print(np.exp(b))
print(np.log(b))
print(np.sin(b))
print(np.e, np.pi)


[ 1.41421356  1.08881569  1.12468265  1.26755256  1.49071198]
[ 7.3890561   3.27238673  3.54277764  4.98627681  9.22781435]
[ 0.69314718  0.17018117  0.23500181  0.47417585  0.7985077 ]
[ 0.90929743  0.92669447  0.95358074  0.99935591  0.79522006]
2.718281828459045 3.141592653589793

Иногда бывает нужно использовать частичные (кумулятивные) суммы. В нашем курсе такое пригодится.


In [48]:
print(b.cumsum())


[ 2.          3.18551961  4.45043067  6.05712017  8.27934239]

Функция sort возвращает отсортированную копию, метод sort сортирует на месте.


In [49]:
print(np.sort(b))
print(b)


[ 1.18551961  1.26491106  1.6066895   2.          2.22222222]
[ 2.          1.18551961  1.26491106  1.6066895   2.22222222]

In [50]:
b.sort()
print(b)


[ 1.18551961  1.26491106  1.6066895   2.          2.22222222]

Объединение массивов.


In [51]:
a = np.hstack((a, b))
print(a)


[ 1.          3.          5.          7.          9.          1.18551961
  1.26491106  1.6066895   2.          2.22222222]

Расщепление массива в позициях 3 и 6.


In [52]:
np.hsplit(a, [3, 6])


Out[52]:
[array([ 1.,  3.,  5.]),
 array([ 7.        ,  9.        ,  1.18551961]),
 array([ 1.26491106,  1.6066895 ,  2.        ,  2.22222222])]

Функции delete, insert и append не меняют массив на месте, а возвращают новый массив, в котором удалены, вставлены в середину или добавлены в конец какие-то элементы.


In [53]:
a = np.delete(a, [5, 7])
print(a)


[ 1.          3.          5.          7.          9.          1.26491106
  2.          2.22222222]

In [54]:
a = np.insert(a, 2, [0, 0])
print(a)


[ 1.          3.          0.          0.          5.          7.          9.
  1.26491106  2.          2.22222222]

In [55]:
a = np.append(a, [1, 2, 3])
print(a)


[ 1.          3.          0.          0.          5.          7.          9.
  1.26491106  2.          2.22222222  1.          2.          3.        ]

Есть несколько способов индексации массива. Вот обычный индекс.


In [56]:
a = np.linspace(0, 1, 11)
print(a)


[ 0.   0.1  0.2  0.3  0.4  0.5  0.6  0.7  0.8  0.9  1. ]

In [57]:
b = a[2]
print(b)


0.2

Диапазон индексов. Создаётся новый заголовок массива, указывающий на те же данные. Изменения, сделанные через такой массив, видны и в исходном массиве.


In [58]:
b = a[2:6]
print(b)


[ 0.2  0.3  0.4  0.5]

In [59]:
b[0] = -0.2
print(b)


[-0.2  0.3  0.4  0.5]

In [60]:
print(a)


[ 0.   0.1 -0.2  0.3  0.4  0.5  0.6  0.7  0.8  0.9  1. ]

Диапазон с шагом 2.


In [61]:
b = a[1:10:2]
print(b)


[ 0.1  0.3  0.5  0.7  0.9]

In [62]:
b[0] = -0.1
print(a)


[ 0.  -0.1 -0.2  0.3  0.4  0.5  0.6  0.7  0.8  0.9  1. ]

Массив в обратном порядке.


In [63]:
b = a[len(a):0:-1]
print(b)


[ 1.   0.9  0.8  0.7  0.6  0.5  0.4  0.3 -0.2 -0.1]

Подмассиву можно присвоить значение - массив правильного размера или скаляр.


In [64]:
a[1:10:3] = 0
print(a)


[ 0.   0.  -0.2  0.3  0.   0.5  0.6  0.   0.8  0.9  1. ]

Тут опять создаётся только новый заголовок, указывающий на те же данные.


In [65]:
b = a[:]
b[1] = 0.1
print(a)


[ 0.   0.1 -0.2  0.3  0.   0.5  0.6  0.   0.8  0.9  1. ]

Чтобы скопировать и данные массива, нужно использовать метод copy.


In [66]:
b = a.copy()
b[2] = 0
print(b)
print(a)


[ 0.   0.1  0.   0.3  0.   0.5  0.6  0.   0.8  0.9  1. ]
[ 0.   0.1 -0.2  0.3  0.   0.5  0.6  0.   0.8  0.9  1. ]

Можно задать список индексов.


In [67]:
print(a[[2, 3, 5]])


[-0.2  0.3  0.5]

Можно задать булев массив той же величины.


In [68]:
b = a > 0
print(b)


[False  True False  True False  True  True False  True  True  True]

In [69]:
print(a[b])


[ 0.1  0.3  0.5  0.6  0.8  0.9  1. ]

2-мерные массивы


In [70]:
a = np.array([[0.0, 1.0], [-1.0, 0.0]])
print(a)


[[ 0.  1.]
 [-1.  0.]]

In [71]:
a.ndim


Out[71]:
2

In [72]:
a.shape


Out[72]:
(2, 2)

In [73]:
len(a), a.size


Out[73]:
(2, 4)

In [74]:
a[1, 0]


Out[74]:
-1.0

Атрибуту shape можно присвоить новое значение - кортеж размеров по всем координатам. Получится новый заголовок массива; его данные не изменятся.


In [75]:
b = np.linspace(0, 3, 4)
print(b)


[ 0.  1.  2.  3.]

In [76]:
b.shape


Out[76]:
(4,)

In [77]:
b.shape = 2, 2
print(b)


[[ 0.  1.]
 [ 2.  3.]]

Можно растянуть в одномерный массив


In [78]:
print(b.ravel())


[ 0.  1.  2.  3.]

Арифметические операции поэлементные


In [79]:
print(a + 1)
print(a * 2)
print(a + [0, 1])  # второе слагаемое дополняется до матрицы копированием строк
print(a + np.array([[0, 2]]).T)  # .T - транспонирование
print(a + b)


[[ 1.  2.]
 [ 0.  1.]]
[[ 0.  2.]
 [-2.  0.]]
[[ 0.  2.]
 [-1.  1.]]
[[ 0.  1.]
 [ 1.  2.]]
[[ 0.  2.]
 [ 1.  3.]]

Поэлементное и матричное (только в Python 3.5) умножение.


In [80]:
print(a * b)


[[ 0.  1.]
 [-2.  0.]]

In [81]:
print(a @ b)


[[ 2.  3.]
 [ 0. -1.]]

In [82]:
print(b @ a)


[[-1.  0.]
 [-3.  2.]]

Умножение матрицы на вектор.


In [83]:
v = np.array([1, -1], dtype=np.float64)
print(b @ v)


[-1. -1.]

In [84]:
print(v @ b)


[-2. -2.]

Если у вас Питон более ранней версии, то для работы с матрицами можно использовать класс np.matrix, в котором операция умножения реализуется как матричное умножение.


In [85]:
np.matrix(a) * np.matrix(b)


Out[85]:
matrix([[ 2.,  3.],
        [ 0., -1.]])

Внешнее произведение $a_{ij}=u_i v_j$


In [86]:
u = np.linspace(1, 2, 2)
v = np.linspace(2, 4, 3)
print(u)
print(v)


[ 1.  2.]
[ 2.  3.  4.]

In [87]:
a = np.outer(u, v)
print(a)


[[ 2.  3.  4.]
 [ 4.  6.  8.]]

Двумерные массивы, зависящие только от одного индекса: $x_{ij}=u_j$, $y_{ij}=v_i$


In [88]:
x, y = np.meshgrid(u, v)
print(x)
print(y)


[[ 1.  2.]
 [ 1.  2.]
 [ 1.  2.]]
[[ 2.  2.]
 [ 3.  3.]
 [ 4.  4.]]

Единичная матрица.


In [89]:
I = np.eye(4)
print(I)


[[ 1.  0.  0.  0.]
 [ 0.  1.  0.  0.]
 [ 0.  0.  1.  0.]
 [ 0.  0.  0.  1.]]

Метод reshape делает то же самое, что присваивание атрибуту shape.


In [90]:
print(I.reshape(16))


[ 1.  0.  0.  0.  0.  1.  0.  0.  0.  0.  1.  0.  0.  0.  0.  1.]

In [91]:
print(I.reshape(2, 8))


[[ 1.  0.  0.  0.  0.  1.  0.  0.]
 [ 0.  0.  1.  0.  0.  0.  0.  1.]]

Строка.


In [92]:
print(I[1])


[ 0.  1.  0.  0.]

Цикл по строкам.


In [93]:
for row in I:
    print(row)


[ 1.  0.  0.  0.]
[ 0.  1.  0.  0.]
[ 0.  0.  1.  0.]
[ 0.  0.  0.  1.]

Столбец.


In [94]:
print(I[:, 2])


[ 0.  0.  1.  0.]

Подматрица.


In [95]:
print(I[0:2, 1:3])


[[ 0.  0.]
 [ 1.  0.]]

Можно построить двумерный массив из функции.


In [96]:
def f(i, j):
    print(i)
    print(j)
    return 10 * i + j

print(np.fromfunction(f, (4, 4), dtype=np.int64))


[[0 0 0 0]
 [1 1 1 1]
 [2 2 2 2]
 [3 3 3 3]]
[[0 1 2 3]
 [0 1 2 3]
 [0 1 2 3]
 [0 1 2 3]]
[[ 0  1  2  3]
 [10 11 12 13]
 [20 21 22 23]
 [30 31 32 33]]

Транспонированная матрица.


In [97]:
print(b.T)


[[ 0.  2.]
 [ 1.  3.]]

Соединение матриц по горизонтали и по вертикали.


In [98]:
a = np.array([[0, 1], [2, 3]])
b = np.array([[4, 5, 6], [7, 8, 9]])
c = np.array([[4, 5], [6, 7], [8, 9]])
print(a)
print(b)
print(c)


[[0 1]
 [2 3]]
[[4 5 6]
 [7 8 9]]
[[4 5]
 [6 7]
 [8 9]]

In [99]:
print(np.hstack((a, b)))


[[0 1 4 5 6]
 [2 3 7 8 9]]

In [100]:
print(np.vstack((a, c)))


[[0 1]
 [2 3]
 [4 5]
 [6 7]
 [8 9]]

Сумма всех элементов; суммы столбцов; суммы строк.


In [101]:
print(b.sum())
print(b.sum(axis=0))
print(b.sum(axis=1))


39
[11 13 15]
[15 24]

Аналогично работают prod, max, min и т.д.


In [102]:
print(b.max())
print(b.max(axis=0))
print(b.min(axis=1))


9
[7 8 9]
[4 7]

След - сумма диагональных элементов.


In [103]:
np.trace(a)


Out[103]:
3

Многомерные массивы


In [104]:
X = np.arange(24).reshape(2, 3, 4)
print(X)


[[[ 0  1  2  3]
  [ 4  5  6  7]
  [ 8  9 10 11]]

 [[12 13 14 15]
  [16 17 18 19]
  [20 21 22 23]]]

Суммирование (аналогично остальные операции)


In [105]:
# суммируем только по нулевой оси, то есть для фиксированных j и k суммируем только элементы с индексами (*, j, k)
print(X.sum(axis=0))
# суммируем сразу по двум осям, то есть для фиксированной i суммируем только элементы с индексами (i, *, *)
print(X.sum(axis=(1, 2)))


[[12 14 16 18]
 [20 22 24 26]
 [28 30 32 34]]
[ 66 210]

Линейная алгебра


In [106]:
np.linalg.det(a)


Out[106]:
-2.0

Обратная матрица.


In [107]:
a1 = np.linalg.inv(a)
print(a1)


[[-1.5  0.5]
 [ 1.   0. ]]

In [108]:
print(a @ a1)
print(a1 @ a)


[[ 1.  0.]
 [ 0.  1.]]
[[ 1.  0.]
 [ 0.  1.]]

Решение линейной системы $au=v$.


In [109]:
v = np.array([0, 1], dtype=np.float64)
print(a1 @ v)


[ 0.5  0. ]

In [110]:
u = np.linalg.solve(a, v)
print(u)


[ 0.5  0. ]

Проверим.


In [111]:
print(a @ u - v)


[ 0.  0.]

Собственные значения и собственные векторы: $a u_i = \lambda_i u_i$. l - одномерный массив собственных значений $\lambda_i$, столбцы матрицы $u$ - собственные векторы $u_i$.


In [112]:
l, u = np.linalg.eig(a)
print(l)


[-0.56155281  3.56155281]

In [113]:
print(u)


[[-0.87192821 -0.27032301]
 [ 0.48963374 -0.96276969]]

Проверим.


In [114]:
for i in range(2):
    print(a @ u[:, i] - l[i] * u[:, i])


[  0.00000000e+00   1.66533454e-16]
[  0.00000000e+00  -4.44089210e-16]

Функция diag от одномерного массива строит диагональную матрицу; от квадратной матрицы - возвращает одномерный массив её диагональных элементов.


In [115]:
L = np.diag(l)
print(L)
print(np.diag(L))


[[-0.56155281  0.        ]
 [ 0.          3.56155281]]
[-0.56155281  3.56155281]

Все уравнения $a u_i = \lambda_i u_i$ можно собрать в одно матричное уравнение $a u = u \Lambda$, где $\Lambda$ - диагональная матрица с собственными значениями $\lambda_i$ по диагонали.


In [116]:
print(a @ u - u @ L)


[[  0.00000000e+00   0.00000000e+00]
 [  1.66533454e-16  -4.44089210e-16]]

Поэтому $u^{-1} a u = \Lambda$.


In [117]:
print(np.linalg.inv(u) @ a @ u)


[[ -5.61552813e-01   2.77555756e-17]
 [ -2.22044605e-16   3.56155281e+00]]

Найдём теперь левые собственные векторы $v_i a = \lambda_i v_i$ (собственные значения $\lambda_i$ те же самые).


In [118]:
l, v = np.linalg.eig(a.T)
print(l)
print(v)


[-0.56155281  3.56155281]
[[-0.96276969 -0.48963374]
 [ 0.27032301 -0.87192821]]

Собственные векторы нормированы на 1.


In [119]:
print(u.T @ u)
print(v.T @ v)


[[ 1.         -0.23570226]
 [-0.23570226  1.        ]]
[[ 1.          0.23570226]
 [ 0.23570226  1.        ]]

Левые и правые собственные векторы, соответствующие разным собственным значениям, ортогональны, потому что $v_i a u_j = \lambda_i v_i u_j = \lambda_j v_i u_j$.


In [120]:
print(v.T @ u)


[[  9.71825316e-01   0.00000000e+00]
 [ -5.55111512e-17   9.71825316e-01]]

Интегрирование


In [121]:
from scipy.integrate import quad, odeint
from scipy.special import erf

In [122]:
def f(x):
    return np.exp(-x ** 2)

Адаптивное численное интегрирование (может быть до бесконечности). err - оценка ошибки.


In [123]:
res, err = quad(f, 0, np.inf)
print(np.sqrt(np.pi) / 2, res, err)


0.886226925453 0.8862269254527579 7.101318390472462e-09

In [124]:
res, err =  quad(f, 0, 1)
print(np.sqrt(np.pi) / 2 * erf(1), res, err)


0.746824132812 0.7468241328124271 8.291413475940725e-15

Сохранение в файл и чтение из файла


In [125]:
x = np.arange(0, 25, 0.5).reshape((5, 10))

# Сохраняем в файл example.txt данные x в формате с двумя точками после запятой и разделителем ';'
np.savetxt('example.txt', x, fmt='%.2f', delimiter=';')

Получится такой файл


In [126]:
! cat example.txt


0.00;0.50;1.00;1.50;2.00;2.50;3.00;3.50;4.00;4.50
5.00;5.50;6.00;6.50;7.00;7.50;8.00;8.50;9.00;9.50
10.00;10.50;11.00;11.50;12.00;12.50;13.00;13.50;14.00;14.50
15.00;15.50;16.00;16.50;17.00;17.50;18.00;18.50;19.00;19.50
20.00;20.50;21.00;21.50;22.00;22.50;23.00;23.50;24.00;24.50

Теперь его можно прочитать


In [127]:
x = np.loadtxt('example.txt', delimiter=';')
print(x)


[[  0.    0.5   1.    1.5   2.    2.5   3.    3.5   4.    4.5]
 [  5.    5.5   6.    6.5   7.    7.5   8.    8.5   9.    9.5]
 [ 10.   10.5  11.   11.5  12.   12.5  13.   13.5  14.   14.5]
 [ 15.   15.5  16.   16.5  17.   17.5  18.   18.5  19.   19.5]
 [ 20.   20.5  21.   21.5  22.   22.5  23.   23.5  24.   24.5]]

Библиотека scipy (модуль scipy.stats)

Нам пригодится только модуль scipy.stats. Полное описание http://docs.scipy.org/doc/scipy/reference/stats.html


In [128]:
import scipy.stats as sps

Общий принцип:

$X$ — некоторое распределение с параметрами params

  • `X.rvs(size=N, params)` — генерация выборки размера $N$ (Random VariateS). Возвращает `numpy.array`
  • `X.cdf(x, params)` — значение функции распределения в точке $x$ (Cumulative Distribution Function)
  • `X.logcdf(x, params)` — значение логарифма функции распределения в точке $x$
  • `X.ppf(q, params)` — $q$-квантиль (Percent Point Function)
  • `X.mean(params)` — математическое ожидание
  • `X.median(params)` — медиана
  • `X.var(params)` — дисперсия (Variance)
  • `X.std(params)` — стандартное отклонение = корень из дисперсии (Standard Deviation)

Кроме того для непрерывных распределений определены функции

  • `X.pdf(x, params)` — значение плотности в точке $x$ (Probability Density Function)
  • `X.logpdf(x, params)` — значение логарифма плотности в точке $x$

А для дискретных

  • `X.pmf(k, params)` — значение дискретной плотности в точке $k$ (Probability Mass Function)
  • `X.logpdf(k, params)` — значение логарифма дискретной плотности в точке $k$

Параметры могут быть следующими:

  • `loc` — параметр сдвига
  • `scale` — параметр масштаба
  • и другие параметры (например, $n$ и $p$ для биномиального)

Для примера сгенерируем выборку размера $N = 200$ из распределения $\mathscr{N}(1, 9)$ и посчитаем некоторые статистики. В терминах выше описанных функций у нас $X$ = sps.norm, а params = (loc=1, scale=3).


In [129]:
sample = sps.norm.rvs(size=200, loc=1, scale=3)
print('Первые 10 значений выборки:\n', sample[:10])
print('Выборочное среденее: %.3f' % sample.mean())
print('Выборочная дисперсия: %.3f' % sample.var())


Первые 10 значений выборки:
 [ 3.28928372  0.82650155  1.79310223  3.96558151  2.41541782  3.10161325
  2.58963169  1.2317635   4.28081739 -1.77051388]
Выборочное среденее: 0.971
Выборочная дисперсия: 7.847

In [130]:
print('Плотность:\t\t', sps.norm.pdf([-1, 0, 1, 2, 3], loc=1, scale=3))
print('Функция распределения:\t', sps.norm.cdf([-1, 0, 1, 2, 3], loc=1, scale=3))


Плотность:		 [ 0.10648267  0.12579441  0.13298076  0.12579441  0.10648267]
Функция распределения:	 [ 0.25249254  0.36944134  0.5         0.63055866  0.74750746]

In [131]:
print('Квантили:', sps.norm.ppf([0.05, 0.1, 0.5, 0.9, 0.95], loc=1, scale=3))


Квантили: [-3.93456088 -2.8446547   1.          4.8446547   5.93456088]

Cгенерируем выборку размера $N = 200$ из распределения $Bin(10, 0.6)$ и посчитаем некоторые статистики. В терминах выше описанных функций у нас $X$ = sps.binom, а params = (n=10, p=0.6).


In [132]:
sample = sps.binom.rvs(size=200, n=10, p=0.6)
print('Первые 10 значений выборки:\n', sample[:10])
print('Выборочное среденее: %.3f' % sample.mean())
print('Выборочная дисперсия: %.3f' % sample.var())


Первые 10 значений выборки:
 [5 7 6 7 3 4 8 7 5 6]
Выборочное среденее: 6.065
Выборочная дисперсия: 2.331

In [133]:
print('Дискретная плотность:\t', sps.binom.pmf([-1, 0, 5, 5.5, 10], n=10, p=0.6))
print('Функция распределения:\t', sps.binom.cdf([-1, 0, 5, 5.5, 10], n=10, p=0.6))


Дискретная плотность:	 [  0.00000000e+00   1.04857600e-04   2.00658125e-01   0.00000000e+00
   6.04661760e-03]
Функция распределения:	 [  0.00000000e+00   1.04857600e-04   3.66896742e-01   3.66896742e-01
   1.00000000e+00]

In [134]:
print('Квантили:', sps.binom.ppf([0.05, 0.1, 0.5, 0.9, 0.95], n=10, p=0.6))


Квантили: [ 3.  4.  6.  8.  8.]

Отдельно есть класс для многомерного нормального распределения. Для примера сгенерируем выборку размера $N=200$ из распределения $\mathscr{N} \left( \begin{pmatrix} 1 \\ 1 \end{pmatrix}, \begin{pmatrix} 2 & 1 \\ 1 & 2 \end{pmatrix} \right)$.


In [135]:
sample = sps.multivariate_normal.rvs(mean=[1, 1], cov=[[2, 1], [1, 2]], size=200)
print('Первые 10 значений выборки:\n', sample[:10])
print('Выборочное среденее:', sample.mean(axis=0))
print('Выборочная матрица ковариаций:\n', np.cov(sample.T))


Первые 10 значений выборки:
 [[-1.9861816  -0.94358461]
 [ 1.93376109  0.34449948]
 [ 1.76689     3.25707287]
 [ 1.14967263 -0.71283847]
 [ 1.44368489  1.27636574]
 [ 1.48994732  2.03350446]
 [ 2.02426618  1.21057156]
 [ 1.67851671  2.30199687]
 [ 1.90705893  2.1001483 ]
 [ 2.96734234  2.58021913]]
Выборочное среденее: [ 1.14018367  0.98307564]
Выборочная матрица ковариаций:
 [[ 2.10650447  0.94076559]
 [ 0.94076559  1.87049463]]

Некоторая хитрость :)


In [136]:
sample = sps.norm.rvs(size=10, loc=np.arange(10), scale=0.1)
print(sample)


[-0.25874425  0.97813837  2.04639019  3.0187115   4.05480661  4.94792113
  6.01970204  7.00142419  7.9675934   8.88900013]

Бывает так, что надо сгенерировать выборку из распределения, которого нет в `scipy.stats`. Для этого надо создать класс, который будет наследоваться от класса rv_continuous для непрерывных случайных величин и от класса rv_discrete для дискретных случайных величин. Пример есть на странице http://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.rv_continuous.html#scipy.stats.rv_continuous

Для примера сгенерируем выборку из распределения с плотностью $f(x) = \frac{4}{15} x^3 I\{x \in [1, 2] = [a, b]\}$.


In [137]:
class cubic_gen(sps.rv_continuous):
    def _pdf(self, x):
        return 4 * x ** 3 / 15
cubic = cubic_gen(a=1, b=2, name='cubic')

sample = cubic.rvs(size=200)
print('Первые 10 значений выборки:\n', sample[:10])
print('Выборочное среденее: %.3f' % sample.mean())
print('Выборочная дисперсия: %.3f' % sample.var())


Первые 10 значений выборки:
 [ 1.8838009   1.80617825  1.09789444  1.65771829  1.72582776  1.57311372
  1.7174875   1.99153808  1.90110246  1.69306301]
Выборочное среденее: 1.652
Выборочная дисперсия: 0.064

Если дискретная случайная величина может принимать небольшое число значений, то можно не создавать новый класс, как показано выше, а явно указать эти значения и из вероятности.


In [138]:
some_distribution = sps.rv_discrete(name='some_distribution', values=([1, 2, 3], [0.6, 0.1, 0.3]))

sample = some_distribution.rvs(size=200)
print('Первые 10 значений выборки:\n', sample[:10])
print('Выборочное среденее: %.3f' % sample.mean())
print('Частота значений по выборке:', (sample == 1).mean(), (sample == 2).mean(), (sample == 3).mean())


Первые 10 значений выборки:
 [3 1 1 3 3 1 3 1 1 1]
Выборочное среденее: 1.725
Частота значений по выборке: 0.575 0.125 0.3