NumPy [/ˈnʌmpaɪ/ (NUM-py)] - библиотека языка Python, добавляющая поддержку многомерных массивов и матриц, а также целую коллекцию формул и высокоуровневых математических функций для работы с массивами и матрицами.
import numpy as np
np.__version__
'1.18.1'
Давайте подумаем, зачем нужен целый модуль для математики, массивов и матриц?
Мне будет достаточно списков! - they said.
numpy
?¶numpy
?¶Сколько программистов нужно, чтобы заменить лампочку?
Ни одного - проблема тут по части железа.
Numpy нужен для векторизованных вычислений!
a = [1, 6, 3, 4, 3, 7]
a = [i + 1 for i in a]
a
[2, 7, 4, 5, 4, 8]
a = np.array([1, 6, 3, 4, 3, 7])
a
array([1, 6, 3, 4, 3, 7])
a + 1
array([2, 7, 4, 5, 4, 8])
Векторизация помогает избавиться от циклов и лишнего кода!
mln = 10 ** 6
a = list(range(mln))
%%timeit
[e * e for e in a]
73.9 ms ± 1.42 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
a = np.arange(mln)
%%timeit
a * a
1.26 ms ± 107 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Какая операция здесь самая медленная?
Конечно же, цикл!
Ещё дольше выполняются вложенные циклы:
rows, cols = 1000, 1000
a = [list(range(i, i + cols)) for i in range(0, rows)]
b = [[0 for j in range(cols)] for i in range(rows)]
%%timeit
for i in range(rows):
for j in range(cols):
b[i][j] = 2 * a[i][j]
126 ms ± 4.12 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
Сравним с векторизованными циклами:
a = np.asarray([list(range(i, i + cols)) for i in range(0, rows)])
%%timeit
2 * a
1.19 ms ± 49.1 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Векторизация помогает ускорить скорость выполнения векторизуемых операций в десятки раз!
numpy.array
¶Почему в массиве важно иметь один и тот же тип данных?
Самый простой способ создать массив numpy.array
- из стандартного списка list
.
mydata = np.array([1, 2, 3])
mydata
array([1, 2, 3])
Массив может быть и многомерным!
mydata_2d = np.array([[1, 2],
[3, 4]])
mydata_2d
array([[1, 2], [3, 4]])
mydata.dtype
dtype('int32')
numpy
¶Тип данных | Описание |
---|---|
bool | Boolean (True or False) stored as a byte |
int | Platform integer (normally either int32 or int64) |
int8 | Byte (-128 to 127) |
int16 | Integer (-32768 to 32767) |
int32 | Integer (-2147483648 to 2147483647) |
int64 | Integer (-9223372036854775808 to 9223372036854775807) |
uint8 | Unsigned integer (0 to 255) |
uint16 | Unsigned integer (0 to 65535) |
uint32 | Unsigned integer (0 to 4294967295) |
uint64 | Unsigned integer (0 to 18446744073709551615) |
float | Shorthand for float64 |
float32 | Single precision float: sign bit, 8 bits exponent, 23 bits mantissa |
float64 | Double precision float: sign bit, 11 bits exponent, 52 bits mantissa |
complex | Shorthand for complex128 |
complex64 | Complex number, represented by two 32-bit floats (real and imaginary components) |
complex128 | Complex number, represented by two 64-bit floats (real and imaginary components) |
mydata = np.array([[1, 2, 3, 4, 5],
[6, 7, 8, 9, 0],
[7, 0, 8, 8, 1]], dtype=np.int8)
mydata
array([[1, 2, 3, 4, 5], [6, 7, 8, 9, 0], [7, 0, 8, 8, 1]], dtype=int8)
len(mydata)
3
mydata.ndim
2
mydata.shape
(3, 5)
numpy.array.shape
¶Что значит "транспонировать матрицу"?
Как транспонировать в стандартном Python?
list_data = [(1, 2, 3), (4, 5, 6), (7, 8, 9)]
list_T = list(zip(*list_data))
list_T
[(1, 4, 7), (2, 5, 8), (3, 6, 9)]
data = np.array(list_data)
data.T
array([[1, 4, 7], [2, 5, 8], [3, 6, 9]])
data.transpose()
array([[1, 4, 7], [2, 5, 8], [3, 6, 9]])
numpy.array
...¶Но сначала... Кто знает, что называют индусским кодом? :^)
int a0 = 0;
int a1 = 0;
int a2 = 0;
int a3 = 0;
int a4 = 0;
int a5 = 0;
int a6 = 0;
int a7 = 0;
int a8 = 0;
int a9 = 0;
Допустим, мы хотим сделать массив numpy.array
из нулей.
Сделаем способом, который помним!
x = 10
y = 3
zeros = []
for i in range(y):
sub_zero = []
for j in range(x):
sub_zero.append(0)
zeros.append(sub_zero)
zeros = np.array(zeros)
zeros
array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])
В стандартном Python:
x = 10
y = 3
zeros = [[0 for _ in range(x)] for _ in range(y)]
zeros
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
[[0] * x] * y
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
np.zeros(shape=(3, 10), dtype=int)
array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])
Чтобы создавать простые массивы numpy.array
, можно воспользоваться специальными функциями:
Аналогично с многомерными массивами:
Аналоги range
, сразу возвращающие массив numpy.array
:
np.arange(1, 10)
array([1, 2, 3, 4, 5, 6, 7, 8, 9])
np.arange(1, 10, 2)
array([1, 3, 5, 7, 9])
np.arange(1, 10, 0.5)
array([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])
np.linspace(0, 1, 5, endpoint=True)
array([0. , 0.25, 0.5 , 0.75, 1. ])
В чем разница?
— Какие храбрые поступки вы совершали в своей жизни?
— Однажды на алгебре я руку поднял.
data = np.array([[1, 2],
[3, 4],
[5, 6]], dtype=np.int8)
Перейдем в интерпретатор
Как вы думаете, что произойдет, если попытаться сложить 2 массива?
С другими операциями это тоже работает:
А что получится с многомерными массивами?
Всё так же!
Можно складывать и разные по размерностям массивы.
data.shape
(3, 2)
data + np.ones(2)
array([[2., 3.], [4., 5.], [6., 7.]])
data + np.ones(3)
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) <ipython-input-38-59adb053f41f> in <module> ----> 1 data + np.ones(3) ValueError: operands could not be broadcast together with shapes (3,2) (3,)
data + np.ones(shape=(3, 1))
array([[2., 3.], [4., 5.], [6., 7.]])
Почему для 2D-массивов 1D-массив прибавляется по axis=1
, но не прибавляется по axis=0
?
На самом деле существуют специальное правило приведения размерностей:
a.shape = (a_1, a_2, ..., a_n)
и b.shape = (b_1, b_2, ..., b_n)
. Над a
и b
можно произвести поэлементую бинарную операцию, если $\forall \; i \in (1...n)$ выполнено хотя бы одно из условий:a_i == b_i
;a_i == 1
;b_i == 1
.Документация: https://docs.scipy.org/doc/numpy/user/basics.broadcasting.html
(из материалов Техносферы@Mail.Ru)
А как будет происходить умножение?
data1 = np.array([[1, 2],
[3, 4]])
data2 = np.array([[0, 10],
[20, -1]])
data1 * data2
array([[ 0, 20], [60, -4]])
Но что делать, если нам нужно матричное умножение?
data = np.array([1, 2, 3])
powers_of_ten = (10 ** np.arange(6)).reshape(3, 2)
powers_of_ten
array([[ 1, 10], [ 100, 1000], [ 10000, 100000]], dtype=int32)
data.dot(powers_of_ten)
array([ 30201, 302010])
Для тех, кто забыл, что такое матричное умножение:
А теперь перейдем от базовой арифметики к чуть более cложным функциям.
Подождите, это все была базовая арифметика?
data = np.array([[1, 2, 3],
[4, 5, 8]])
np.sqrt(data)
array([[1. , 1.41421356, 1.73205081], [2. , 2.23606798, 2.82842712]])
np.exp(data)
array([[2.71828183e+00, 7.38905610e+00, 2.00855369e+01], [5.45981500e+01, 1.48413159e+02, 2.98095799e+03]])
np.log(data)
array([[0. , 0.69314718, 1.09861229], [1.38629436, 1.60943791, 2.07944154]])
np.log2(data)
array([[0. , 1. , 1.5849625 ], [2. , 2.32192809, 3. ]])
np.random.seed(777)
a = np.random.randint(0, 6, size=(8,))
a[3] = 10
a
array([ 3, 1, 5, 10, 1, 2, 0, 2])
a.min(), a.max(), a.argmax(), a.sum(), a.prod(), a.mean()
(0, 10, 3, 24, 0, 3.0)
np.min(a), np.max(a), np.argmax(a), np.sum(a), np.prod(a), np.mean(a)
(0, 10, 3, 24, 0, 3.0)
Старайтесь не использовать с массивами numpy.array
нативные функции min
, max
и sum
.
Какая у этого причина?
a = np.random.randint(-2, 8, size=(6,))
max(a), a
(5, array([-2, -1, 0, 2, 3, 5]))
a = np.random.randint(-2, 8, size=(4, 3))
max(a), a
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) <ipython-input-54-5c91eed52270> in <module> 1 a = np.random.randint(-2, 8, size=(4, 3)) ----> 2 max(a), a ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
Почему агрегирующие операции?
Просто потому что они выполняют агрегацию (редукцию) по определенной оси!
Разберем, что делает numpy.array.???(axis=axis)
– агрегирующая операция вдоль оси axis
:
axis
;axis
из исходного массива.Что все это значит? Как это применять?
Разберем пример:
data = np.array([[1, 2],
[5, 3],
[4, 6]], dtype=int)
data.max(axis=0)
array([5, 6])
data.max(axis=1)
array([2, 5, 6])
np.random.seed(777)
data = np.random.randint(0, 10, size=(3, 7))
data[1, 3] = 15
data
array([[ 7, 6, 7, 1, 7, 4, 7], [ 9, 8, 7, 15, 0, 1, 2], [ 4, 5, 7, 1, 7, 2, 2]])
data.argmax()
10
data = np.arange(6)
data
array([0, 1, 2, 3, 4, 5])
data = data > 2
data
array([False, False, False, True, True, True])
data.any(), np.any(data), any(data)
(True, True, True)
data.all(), np.all(data), all(data)
(False, False, False)
~data
array([ True, True, True, False, False, False])
data = np.arange(12).reshape(3, 4) >= 3
data
array([[False, False, False, True], [ True, True, True, True], [ True, True, True, True]])
data.all()
False
data.all(axis=0), data.all(axis=1)
(array([False, False, False, True]), array([False, True, True]))
all(data)
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) <ipython-input-68-4eb86e975eae> in <module> ----> 1 all(data) ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
a = np.array([1, 1, 0, 0, 1], dtype=bool)
b = np.array([1, 0, 0, 1, 0], dtype=bool)
a and b
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) <ipython-input-70-61df3bd186ad> in <module> ----> 1 a and b ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
a & b
array([ True, False, False, False, False])
a | b
array([ True, True, False, True, True])
a ^ b
array([False, True, False, True, True])
np.random.seed(777)
np.random.rand(10)
array([0.15266373, 0.30235661, 0.06203641, 0.45986034, 0.83525338, 0.92699705, 0.72698898, 0.76849622, 0.26920507, 0.64402929])
np.random.randint(0, 10, 10)
array([1, 2, 4, 5, 7, 1, 7, 2, 2, 7])
np.random.permutation(10)
array([1, 5, 0, 3, 9, 7, 2, 6, 8, 4])
np.random.choice(10, size=10)
array([8, 3, 2, 0, 3, 3, 4, 0, 6, 5])
np.random.seed(777)
data = np.random.randint(1, 20, 10)
data
array([ 8, 16, 7, 18, 8, 8, 15, 8, 19, 14])
np.sort(data), data
(array([ 7, 8, 8, 8, 8, 14, 15, 16, 18, 19]), array([ 8, 16, 7, 18, 8, 8, 15, 8, 19, 14]))
data.sort(), data
(None, array([ 7, 8, 8, 8, 8, 14, 15, 16, 18, 19]))
Matplotlib -- одна из самых популярных библиотек для визуализации данных в Python.
import matplotlib.pyplot as plt
a = np.array([1, 6, 3, 4, 3, 7, 2, 3, 2, 1, 10, 12, 0])
a
array([ 1, 6, 3, 4, 3, 7, 2, 3, 2, 1, 10, 12, 0])
plt.plot(a)
[<matplotlib.lines.Line2D at 0x1d4394bb148>]
plt.plot(a + 3)
plt.plot(np.sqrt(a))
[<matplotlib.lines.Line2D at 0x1d439d71ec8>]
x = np.linspace(-np.pi, np.pi, 100)
y = np.sin(x)
plt.plot(y)
[<matplotlib.lines.Line2D at 0x1d439e2e688>]
Что-то не так: по оси X у нас неверные координаты. Кто виноват? Что делать?
plt.plot(x, y)
[<matplotlib.lines.Line2D at 0x1d439e9d148>]
Если plt.plot
передан один аргумент, то x
принимается за индексы массива!
А если у нас точки неупорядоченные?
x = np.random.normal(size=100)
y = np.random.normal(size=100)
plt.plot(x, y)
[<matplotlib.lines.Line2D at 0x1d439f0dc48>]
Что-то не то...
plt.plot(x, y, "o")
[<matplotlib.lines.Line2D at 0x1d439f6bf88>]
Третий аргумент plt.plot
имеет множество функций. Одна из них - выбрать стиль маркера для точек.
plt.plot(x, y, "r")
[<matplotlib.lines.Line2D at 0x1d439fce588>]
А еще он может определять цвет...
plt.plot(x, y, "--")
[<matplotlib.lines.Line2D at 0x1d43a02cb48>]
Стиль линии...
plt.plot(x, y, "gx")
[<matplotlib.lines.Line2D at 0x1d43a095248>]
Или все вместе!
x = np.random.normal(size=100)
y = np.random.normal(size=100)
plt.plot(x, y, "rx")
x = np.random.normal(size=100)
y = np.random.normal(size=100)
plt.plot(x, y, "b+")
[<matplotlib.lines.Line2D at 0x1d439fb4848>]
Также это можно задать параметрами:
plt.plot(x, y, marker="o", color="navy", linestyle="")
[<matplotlib.lines.Line2D at 0x1d43a14cd08>]
x = np.linspace(-np.pi / 2, np.pi / 2, 361)
y = np.tan(x)
plt.plot(x, y)
[<matplotlib.lines.Line2D at 0x1d43a1ae848>]
Обратите внимание на ось Y!
plt.plot(x, y)
plt.ylim(-10, 10)
(-10, 10)
plt.plot(x, y)
plt.ylim(-10, 10)
plt.xlabel("X")
plt.ylabel("tan X")
plt.title("Tangent of X")
Text(0.5, 1.0, 'Tangent of X')
Деления по осям тоже не очень красивые
plt.plot(x, y)
plt.ylim(-10, 10)
plt.xticks([-np.pi / 2, -np.pi / 4, 0, np.pi / 4, np.pi / 2],
["$-\pi/2$", "$-\pi/4$", "0", "$\pi/4$", "$\pi/2$"])
plt.yticks([-10, -5, -2, 0, 2, 5, 10])
plt.xlabel("X")
plt.ylabel("tan X")
plt.title("Тангенс икс")
Text(0.5, 1.0, 'Тангенс икс')
Теперь сделаем саму линию толстого зеленого цвета и пунктирной толщины.
plt.plot(x, y, ls=":", lw=4, color="green")
plt.ylim(-10, 10)
plt.xticks([-np.pi / 2, -np.pi / 4, 0, np.pi / 4, np.pi / 2],
["$-\pi/2$", "$-\pi/4$", "0", "$\pi/4$", "$\pi/2$"])
plt.yticks([-10, -5, -2, 0, 2, 5, 10])
plt.xlabel("X")
plt.ylabel("tan X")
plt.title("Тангенс икс")
Text(0.5, 1.0, 'Тангенс икс')
Вам обычно достаточно немного поменять функцию, и вы получите другой график!
Основная проблема обычно заключается в том, как выбрать, какой тип графика строить для ваших данных.
Disclaimer: это очень упрощенная схема выбора графика, на самом деле графиков существует огромное множество, для ваших данных может подойти какой-то другой график.
data = {"2016": 105, "2016": 117,
"2017": 126, "2018": 132,
"2019": 149, "2020": 130,
"2021": 146}
x = list(data.keys())
y = list(data.values())
plt.plot(x, y, "kD-")
plt.xlabel("Год")
plt.ylabel("Продажи (млн. р.)")
plt.title("Тренд продаж компании X за 6 лет")
plt.show()
plt.plot(x, y, marker="D")
plt.ylim(0, 170)
plt.xlabel("Год")
plt.ylabel("Продажи (млн. р.)")
plt.title("Тренд продаж компании X за 6 лет")
plt.show()
harvest = np.array([[0.8, 2.4, 2.5, 3.9, 0.0, 4.0, 0.0],
[2.4, 0.0, 4.0, 1.0, 2.7, 0.0, 0.0],
[1.1, 2.4, 0.8, 4.3, 1.9, 4.4, 0.0],
[0.6, 0.0, 0.3, 0.0, 3.1, 0.0, 0.0],
[0.7, 1.7, 0.6, 2.6, 2.2, 6.2, 0.0],
[1.3, 1.2, 0.0, 0.0, 0.0, 3.2, 5.1],
[0.1, 2.0, 0.0, 1.4, 0.0, 1.9, 6.3]])
# taken from https://matplotlib.org/stable/gallery/images_contours_and_fields/image_annotated_heatmap.html
im = plt.imshow(harvest)
im = plt.imshow(harvest, cmap="summer")
parts = {"Apple": 18, "Alphabet": 16,
"Microsoft": 15, "Amazon": 13,
"Facebook": 11}
x = list(parts.values())
text = list(parts.keys())
plt.pie(x, labels=text)
plt.show()
Это пример плохого графика! Круговые диаграммы в научной среде никто не любит и строить их не стоит! =)
Но если вы все-таки решили строить pie chart, то обязательно учитывайте полную долю!
others = 100 - sum(x)
parts.update(Others=others)
x = list(parts.values())
text = list(parts.keys())
plt.pie(x, labels=text, startangle=90, counterclock=False, colors=None)
plt.show()
parts = {"Apple": 18000, "Alphabet": 16300,
"Microsoft": 13220, "Amazon": 12900,
"Facebook": 11000}
x = list(parts.values())
text = list(parts.keys())
plt.bar(text, x)
<BarContainer object of 5 artists>
plt.barh(text[::-1], x[::-1])
plt.xlim(10000, 20000)
plt.xticks([10000, 12500, 15000, 17500, 20000])
([<matplotlib.axis.XTick at 0x1d43a55db48>, <matplotlib.axis.XTick at 0x1d43a55d188>, <matplotlib.axis.XTick at 0x1d43a55ad48>, <matplotlib.axis.XTick at 0x1d43a596a08>, <matplotlib.axis.XTick at 0x1d43a598108>], <a list of 5 Text xticklabel objects>)
plt.barh(text[::-1], x[::-1])
plt.xticks([0, 5000, 10000, 15000, 20000])
([<matplotlib.axis.XTick at 0x1d43a5c0088>, <matplotlib.axis.XTick at 0x1d43a5bd7c8>, <matplotlib.axis.XTick at 0x1d43a5bd388>, <matplotlib.axis.XTick at 0x1d43a5f75c8>, <matplotlib.axis.XTick at 0x1d43a5f7d88>], <a list of 5 Text xticklabel objects>)
rvs = np.random.normal(size=1000)
plt.hist(rvs)
plt.show()
from scipy import stats
kde = stats.gaussian_kde(rvs)
plt.hist(rvs, bins=30, density=True)
x_min, x_max = plt.xlim()
x_kde = np.linspace(x_min, x_max, 100)
plt.plot(x_kde, kde(x_kde), color="k")
# In `seaborn` this is automatic! Use seaborn =D
plt.show()
x = np.random.normal(0, 1, 100)
y = (x + np.random.normal(0, 1, 100)) / 2
plt.scatter(x, y, color="k", marker="x")
<matplotlib.collections.PathCollection at 0x1d43b5a8488>
m, b = np.polyfit(x, y, 1) # Regression
x0, x1 = x.min(), x.max()
y0, y1 = m * x0 + b, m * x1 + b
plt.scatter(x, y, color="grey", marker="x")
plt.plot([x0, x1], [y0, y1], color="black", ls="--")
print(f"y={m:.2f}x{b:+.2f}")
y=0.53x+0.00
x = np.random.normal(0, 1, 10000)
y = np.random.normal(0, 1, 10000)
plt.scatter(x, y)
<matplotlib.collections.PathCollection at 0x1d43b68fec8>
plt.scatter(x, y, marker=".", alpha=0.1)
<matplotlib.collections.PathCollection at 0x1d43b6ffc48>
a = np.random.normal(0, size=50)
b = np.random.normal(1.2, size=190)
c = np.random.normal(0.7, 2, size=101)
d = np.random.normal(-1, 0.2, size=120)
plt.boxplot([a, b, c, d])
plt.show()
plt.boxplot([a, b, c, d],
vert=False,
labels=["Alpha", "Beta", "Gamma", "Delta"])
plt.axvline(0, ls="--", lw=1, color="k")
plt.show()
Полную документацию NumPy смотрите на сайте: https://docs.scipy.org/doc/numpy
Полную документацию MPL смотрите на сайте: https://matplotlib.org/stable/contents.html
В NumPy и MPL есть еще множество потенциально полезных для вас функций, так что не поленитесь зайти и посмотреть :^)