Основы программирования в Python

Алла Тамбовцева

Основы работы с библиотекой numpy

Знакомство с массивами

Сегодня мы познакомимся с библиотекой numpy (сокращение от numeric Python), которая часто используется в задачах, связанных с машинным обучением и построением статистических моделей.

Массивы numpy очень похожи на списки (даже больше на вложенные списки), только они имеют одну особенность: элементы массива должны быть одного типа. Либо все элементы целые числа, либо числа с плавающей точкой, либо строки. Для обычных списков это условие не является обязательным:

In [1]:
L = [1, 2, 4, 0]
E = [[1, 0, 3], [3, 6, 7], []]
D = [[1, 3, 6], ['a', 'b', 'c']]

# все работает
print(L)
print(E)
print(D)
[1, 2, 4, 0]
[[1, 0, 3], [3, 6, 7], []]
[[1, 3, 6], ['a', 'b', 'c']]

Чем хороши массивы numpy? Почему обычных списков недостаточно? Во-первых, обработка массивов занимает меньше времени (а их хранение меньше памяти), что очень актуально в случае работы с большими объемами данных. Во-вторых, функции numpy являются векторизованными ‒ их можно применять сразу ко всему массиву, то есть поэлементно. В этом смысле работа с массивами напоминает работу с векторами в R. Если в R у нас есть вектор c(1, 2, 5), то, прогнав строчку кода c(1, 2, 5)**2, мы получим вектор, состоящий из квадратов значений: c(1, 4, 25). Со списками в Python такое проделать не получится: понадобятся циклы или списковые включения. Зато с массивами numpy ‒ легко, и без всяких циклов! И в этом мы сегодня убедимся.

Для начала импортируем библиотеку (и сократим название до np):

In [2]:
import numpy as np

Получить массив numpy можно из обычного списка, просто используя функцию array():

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

Как видно из примера выше, список значений можно просто вписать в array(). Главное не забыть квадратные скобки: Python не сможет склеить перечень элементов в список самостоятельно и выдаст ошибку:

In [5]:
A = np.array(1, 2, 4, 0)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-5-83a5f8fa93ae> in <module>()
----> 1 A = np.array(1, 2, 4, 0)

ValueError: only 2 non-keyword arguments accepted

Посмотрим, какую информацию о массиве можно получить. Например, тип его элементов:

In [6]:
A.dtype # integer
Out[6]:
dtype('int64')

Число измерений ‒ число "маленьких" массивов внутри "большого" массива (здесь такой один).

In [7]:
A.ndim
Out[7]:
1

"Форма" массива, о котором можно думать как о размерности матрицы ‒ кортеж, включающий число строк и столбцов. Здесь у нас всего одна строка, поэтому numpy считает только число элементов внутри массива.

In [8]:
A.shape
Out[8]:
(4,)

Так как массив A одномерный, обращаться к его элементам можно так же, как и к элементам списка, указывая индекс элемента в квадратных скобках:

In [9]:
A[0]
Out[9]:
1

Попытка использовать двойной индекс приведет к неудаче:

In [10]:
A[0][0] # index error
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-10-ac48a874bd97> in <module>()
----> 1 A[0][0] # index error

IndexError: invalid index to scalar variable.

Общее число элементов в массиве можно получить с помощью метода size (аналог len() для списков):

In [11]:
A.size
Out[11]:
4

Кроме того, по массиву можно получить разные описательные статистики:

In [12]:
A.max() # максимум
Out[12]:
4
In [13]:
A.min() # минимум
Out[13]:
0
In [14]:
A.mean() # среднее
Out[14]:
1.75

О других полезных методах можно узнать, нажав Tab после np..

Наконец, массив numpy можно легко превратить в список:

In [15]:
A.tolist()
Out[15]:
[1, 2, 4, 0]

А теперь перейдем к многомерным массивам.

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

Создадим многомерный массив, взяв за основу вложенный список:

In [16]:
S = np.array([[8, 1, 2], [2, 8, 9]])
In [17]:
S
Out[17]:
array([[8, 1, 2],
       [2, 8, 9]])

Посмотрим на число измерений:

In [18]:
S.ndim # два массива внутри
Out[18]:
2
In [19]:
S.shape # две строки (два списка) и три столбца (по три элемента в списке)
Out[19]:
(2, 3)

Общее число элементов в массиве (его длина):

In [20]:
S.size
Out[20]:
6

Когда в массиве больше одного измерения, при различных операциях нужно указывать, по какому измерению мы движемся (по строкам или по столбцам). Посмотрим еще раз на массив S и подумаем о нем как о матрице, как о таблице с числами:

In [21]:
S
Out[21]:
array([[8, 1, 2],
       [2, 8, 9]])

Можно найти максимальное значение по строкам или столбцам S:

In [22]:
S.max(axis=0) # по столбцам - три столбца и три максимальных значения
Out[22]:
array([8, 8, 9])
In [23]:
S.max(axis=1) # по строкам - две строки и два максимальных значения
Out[23]:
array([8, 9])
In [24]:
S.mean(axis=0)
Out[24]:
array([5. , 4.5, 5.5])
In [25]:
S.mean(axis=1)
Out[25]:
array([3.66666667, 6.33333333])

Для того, чтобы обратиться к элементу двумерного массива, нужно указывать два индекса: сначала индекс массива, в котором находится нужный нам элемент, а затем индекс элемента внутри этого массива:

In [26]:
S[0][0]
Out[26]:
8
In [27]:
S[1][2]
Out[27]:
9

Если мы оставим один индекс, мы просто получим массив с соответствующим индексом:

In [28]:
S[0]
Out[28]:
array([8, 1, 2])

Массивы ‒ изменяемые объекты в Python. Обращаясь к элементу массива, ему можно присвоить новое значение:

In [29]:
S[1][2] = 6
S
Out[29]:
array([[8, 1, 2],
       [2, 8, 6]])

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

In [30]:
T = np.array([[1, 3, 7], [8, 10, 1], [2, 8, 9], [1, 0, 5]])
In [31]:
T
Out[31]:
array([[ 1,  3,  7],
       [ 8, 10,  1],
       [ 2,  8,  9],
       [ 1,  0,  5]])

Как и при выборе среза из списка, правый конец не включается:

In [32]:
T[0:2] # массивы с индексами 0 и 1
Out[32]:
array([[ 1,  3,  7],
       [ 8, 10,  1]])

Можно сделать что-то еще более интересное ‒ выставить шаг среза. Другими словами, сообщить Python, что нужно брать? например, элементы, начиная с нулевого, с шагом 2: элемент с индексом 0, с индексом 2, с индексом 4, и так до конца массива.

In [33]:
T[0::2] # старт, двоеточие, двоеточие, шаг
Out[33]:
array([[1, 3, 7],
       [2, 8, 9]])

В примере выше совершенно логично были выбраны элементы с индексами 0 и 2.

Как создать массив?

Способ 1

С первым способом мы уже отчасти познакомились: можно получить массив из готового списка, воспользовавшись функцие array():

In [34]:
np.array([10.5, 45, 2.4])
Out[34]:
array([10.5, 45. ,  2.4])

Кроме того, при создании массива из списка можно изменить его форму, используя функцию reshape().

In [35]:
old = np.array([[2, 5, 6], [9, 8, 0]])
old 
Out[35]:
array([[2, 5, 6],
       [9, 8, 0]])
In [36]:
old.shape # 2 на 3
Out[36]:
(2, 3)
In [37]:
new = old.reshape(3, 2) # изменим на 3 на 2
new
Out[37]:
array([[2, 5],
       [6, 9],
       [8, 0]])
In [38]:
new.shape # 3 на 2
Out[38]:
(3, 2)

Конечно, такие преобразования разумно применять, если произведение чисел в reshape() совпадает с общим числом элементов в массиве. В нашем случае в массиве old 6 элементов, поэтому из него можно получить массивы 2 на 3, 3 на 2, 1 на 6, 6 на 1. Несоответствующее число измерений приведет к ошибке:

In [39]:
old.reshape(2, 4) # и Python явно пишет, что не так
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-39-a803468708c0> in <module>()
----> 1 old.reshape(2, 4) # и Python явно пишет, что не так

ValueError: cannot reshape array of size 6 into shape (2,4)

Способ 2

Можно создать массив на основе промежутка, созданного с помощьюarange() ‒ функции из numpy, похожей на range(), только более гибкую. Посмотрим, как работает эта функция.

In [40]:
np.arange(2, 9) # по умолчанию - как обычный range()
Out[40]:
array([2, 3, 4, 5, 6, 7, 8])

По умолчанию эта функция создает массив, элементы которого начинаются со значения 2 и заканчиваются на значении 8 (правый конец промежутка не включается), следуя друг за другом с шагом 1. Но этот шаг можно менять:

In [41]:
np.arange(2, 9, 3) # с шагом 3
Out[41]:
array([2, 5, 8])

И даже делать дробным!

In [42]:
np.arange(2, 9, 0.5)
Out[42]:
array([2. , 2.5, 3. , 3.5, 4. , 4.5, 5. , 5.5, 6. , 6.5, 7. , 7.5, 8. ,
       8.5])

А теперь совместим arange() и reshape(), чтобы создать массив нужного вида:

In [43]:
np.arange(2, 9, 0.5).reshape(2, 7)
Out[43]:
array([[2. , 2.5, 3. , 3.5, 4. , 4.5, 5. ],
       [5.5, 6. , 6.5, 7. , 7.5, 8. , 8.5]])

Получилось!

Способ 3

Еще массив можно создать совсем с нуля. Единственное, что нужно четко представлять ‒ это его размерность, его форму, то есть опять же, число строк и столбцов. Библиотека numpy позволяет создать массивы, состоящие из нулей или единиц, а также "пустые" массивы (на самом деле, не совсем пустые, как убедимся позже). Удобство заключается в том, что сначала можно создать массив, инициализировать его (например, заполнить нулями), а затем заменить нули на другие значения в соответствии с требуемыми условиями. Как мы помним, массивы ‒ изменяемые объекты, и использовать замену в цикле еще никто не запрещал.

Так выглядит массив из нулей:

In [44]:
Z = np.zeros((3, 3)) # размеры в виде кортежа - не теряйте еще одни круглые скобки
Z
Out[44]:
array([[0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.]])

А так ‒ массив из единиц:

In [45]:
O = np.ones((4, 2))
O
Out[45]:
array([[1., 1.],
       [1., 1.],
       [1., 1.],
       [1., 1.]])

С пустым (empty) массивом все более загадочно:

In [46]:
Emp = np.empty((3, 2))
Emp
Out[46]:
array([[6.92198590e-310, 6.92198590e-310],
       [5.31021756e-317, 6.92194731e-310],
       [5.39590831e-317, 5.39790038e-317]])

Массив Emp ‒ не совсем пустой, в нем содержатся какие-то (псевдо)случайные элементы, которые примерно равны 0. Теоретически создавать массив таким образом можно, но не рекомендуется: лучше создать массив из "чистых" нулей, чем из какого-то непонятного "мусора".

Задание: Дан массив ages (см. ниже). Напишите программу с циклом, которая позволит получить массив ages_bin такой же размерности, что и ages, состоящий из 0 и 1 (0 - младше 18, 1 - не младше 18).

Подсказка: используйте вложенный цикл.

In [47]:
ages = np.array([[12, 16, 17, 18, 14], [20, 22, 18, 17, 23], [32, 16, 44, 16, 23]])

Решение:

In [48]:
shape = ages.shape
ages_bin = np.zeros(shape)
ages_bin

for i in range(0, shape[0]):
    for j in range(0, shape[1]):
        if ages[i][j] >= 18:
            ages_bin[i][j] = 1
ages_bin
Out[48]:
array([[0., 0., 0., 1., 0.],
       [1., 1., 1., 0., 1.],
       [1., 0., 1., 0., 1.]])

Почему массивы numpy ‒ это удобно?

Как уже было отмечено в начале занятия, операции с массивами можно производить поэлементно, не используя циклы или их аналоги. Посмотрим на массив A:

In [49]:
A
Out[49]:
array([1, 2, 4, 0])

А теперь возведем все его элементы в квадрат:

In [50]:
A ** 2
Out[50]:
array([ 1,  4, 16,  0])

Или вычтем из всех элементов единицу:

In [51]:
A - 1
Out[51]:
array([ 0,  1,  3, -1])

Кроме того, так же просто к элементам массива можно применять свои функции. Напишем функцию, которая будет добавлять к элементу 1, а затем считать от него натуральный логарифм (здесь эта функция актуальна, так как в массиве A есть 0).

In [52]:
def my_log(x):
    return np.log(x + 1)

Применим:

In [53]:
my_log(A)
Out[53]:
array([0.69314718, 1.09861229, 1.60943791, 0.        ])

И никаких циклов и иных нагромождений.

Превратить многомерный массив в одномерный (как список) можно, воспользовавшись методами flatten() и ravel().

In [55]:
ages.flatten() # "плоский" массив
Out[55]:
array([12, 16, 17, 18, 14, 20, 22, 18, 17, 23, 32, 16, 44, 16, 23])
In [56]:
ages.ravel()
Out[56]:
array([12, 16, 17, 18, 14, 20, 22, 18, 17, 23, 32, 16, 44, 16, 23])

Чем еще хорош numpy?

1.Позволяет производить вычисления ‒ нет необходимости дополнительно загружать модуль math.

In [57]:
np.log(3) # натуральный логарифм
Out[57]:
1.0986122886681098
In [58]:
np.sqrt(7) # квадратный корень
Out[58]:
2.6457513110645907
In [59]:
np.exp(2) # e^2
Out[59]:
7.38905609893065

2.Позволяет производить операции с векторами и матрицами. Пусть у нас есть два вектора a и b.

In [60]:
a = np.array([1, 2, 3])
b = np.array([0, 4, 7])

Если мы просто умножим a на b с помощью символа *, мы получим массив, содержащий произведения соответствующих элементов a и b:

In [61]:
a * b
Out[61]:
array([ 0,  8, 21])

А если мы воспользуемся функцией dot(), получится скалярное произведение векторов (dot product).

In [62]:
np.dot(a, b) # результат - число
Out[62]:
29

При желании можно получить векторное произведение (cross product):

In [63]:
np.cross(a, b) # результат- вектор
Out[63]:
array([ 2, -7,  4])

Теперь создадим матрицу и поработаем с ней. Создадим ее не самым интуитивным образов ‒ из строки (да, так тоже можно).

In [64]:
m = np.array(np.mat('2 4; 1 6')) # np.mat - матрица из строки, np.array - массив из матрицы 

Самое простоеи понятное, что можно сделать с матрицей ‒ транспонировать ее, то есть поменять местами строки и столбцы:

In [65]:
m.T 
Out[65]:
array([[2, 1],
       [4, 6]])

Можно вывести ее диагональные элементы:

In [66]:
m.diagonal()
Out[66]:
array([2, 6])

И посчитать след матрицы ‒ сумму ее диагональных элементов:

In [67]:
m.trace()
Out[67]:
8

Задание. Создайте единичную матрицу 3 на 3, создав массив из нулей, а затем заполнив ее диагональные элементы значениями 1.

Подсказка: функция fill_diagonal().

Решение:

In [70]:
I = np.zeros((3, 3))
np.fill_diagonal(I, 1)
I
Out[70]:
array([[1., 0., 0.],
       [0., 1., 0.],
       [0., 0., 1.]])

Правда, для создания массива в виде единичной матрицы в numpy уже есть готовая функция (наряду с zeros и ones):

In [71]:
np.eye(3)
Out[71]:
array([[1., 0., 0.],
       [0., 1., 0.],
       [0., 0., 1.]])
In [72]:
np.invert(m)
Out[72]:
array([[-3, -5],
       [-2, -7]])

Для других операций с матрицами (и вычислений в рамках линейной алгебры) можно использовать функции из подмодуля linalg. Например, так можно найти определитель матрицы:

In [73]:
np.linalg.det(m) # вспоминаем истории про числа с плавающей точкой, это 8 на самом деле
Out[73]:
7.999999999999998

И собственные значения:

In [74]:
np.linalg.eigvals(m)
Out[74]:
array([1.17157288, 6.82842712])

Полный список функций с описанием см. в документации.

3.Библиотеку numpy часто используют с библиотекой для визуализации matplotlib.

Рассмотрим функцию linspace(). Она возвращает массив одинаково (с одинаковым шагом) распределенных чисел из фиксированного интервала.

In [76]:
x = np.linspace(10, 100, 5) # 5 чисел из интервала от 10 до 100
x
Out[76]:
array([ 10. ,  32.5,  55. ,  77.5, 100. ])
In [78]:
x = np.linspace(0, 1000, 100) 
x
Out[78]:
array([   0.        ,   10.1010101 ,   20.2020202 ,   30.3030303 ,
         40.4040404 ,   50.50505051,   60.60606061,   70.70707071,
         80.80808081,   90.90909091,  101.01010101,  111.11111111,
        121.21212121,  131.31313131,  141.41414141,  151.51515152,
        161.61616162,  171.71717172,  181.81818182,  191.91919192,
        202.02020202,  212.12121212,  222.22222222,  232.32323232,
        242.42424242,  252.52525253,  262.62626263,  272.72727273,
        282.82828283,  292.92929293,  303.03030303,  313.13131313,
        323.23232323,  333.33333333,  343.43434343,  353.53535354,
        363.63636364,  373.73737374,  383.83838384,  393.93939394,
        404.04040404,  414.14141414,  424.24242424,  434.34343434,
        444.44444444,  454.54545455,  464.64646465,  474.74747475,
        484.84848485,  494.94949495,  505.05050505,  515.15151515,
        525.25252525,  535.35353535,  545.45454545,  555.55555556,
        565.65656566,  575.75757576,  585.85858586,  595.95959596,
        606.06060606,  616.16161616,  626.26262626,  636.36363636,
        646.46464646,  656.56565657,  666.66666667,  676.76767677,
        686.86868687,  696.96969697,  707.07070707,  717.17171717,
        727.27272727,  737.37373737,  747.47474747,  757.57575758,
        767.67676768,  777.77777778,  787.87878788,  797.97979798,
        808.08080808,  818.18181818,  828.28282828,  838.38383838,
        848.48484848,  858.58585859,  868.68686869,  878.78787879,
        888.88888889,  898.98989899,  909.09090909,  919.19191919,
        929.29292929,  939.39393939,  949.49494949,  959.5959596 ,
        969.6969697 ,  979.7979798 ,  989.8989899 , 1000.        ])

При чем тут matplotlib? Представьте, что нам нужно построить обычный график функции $y=x^2$. Если в наши задачи не входит построение графика по определенным данным, можно спокойно создать массив x с помощью linspace, а затем просто возвести его в квадрат! И нанести полученные точки на график.

In [79]:
y = x ** 2
y
Out[79]:
array([0.00000000e+00, 1.02030405e+02, 4.08121620e+02, 9.18273646e+02,
       1.63248648e+03, 2.55076013e+03, 3.67309458e+03, 4.99948985e+03,
       6.52994592e+03, 8.26446281e+03, 1.02030405e+04, 1.23456790e+04,
       1.46923783e+04, 1.72431385e+04, 1.99979594e+04, 2.29568411e+04,
       2.61197837e+04, 2.94867871e+04, 3.30578512e+04, 3.68329762e+04,
       4.08121620e+04, 4.49954086e+04, 4.93827160e+04, 5.39740843e+04,
       5.87695133e+04, 6.37690032e+04, 6.89725538e+04, 7.43801653e+04,
       7.99918376e+04, 8.58075707e+04, 9.18273646e+04, 9.80512193e+04,
       1.04479135e+05, 1.11111111e+05, 1.17947148e+05, 1.24987246e+05,
       1.32231405e+05, 1.39679625e+05, 1.47331905e+05, 1.55188246e+05,
       1.63248648e+05, 1.71513111e+05, 1.79981635e+05, 1.88654219e+05,
       1.97530864e+05, 2.06611570e+05, 2.15896337e+05, 2.25385165e+05,
       2.35078053e+05, 2.44975003e+05, 2.55076013e+05, 2.65381084e+05,
       2.75890215e+05, 2.86603408e+05, 2.97520661e+05, 3.08641975e+05,
       3.19967350e+05, 3.31496786e+05, 3.43230283e+05, 3.55167840e+05,
       3.67309458e+05, 3.79655137e+05, 3.92204877e+05, 4.04958678e+05,
       4.17916539e+05, 4.31078461e+05, 4.44444444e+05, 4.58014488e+05,
       4.71788593e+05, 4.85766758e+05, 4.99948985e+05, 5.14335272e+05,
       5.28925620e+05, 5.43720029e+05, 5.58718498e+05, 5.73921028e+05,
       5.89327620e+05, 6.04938272e+05, 6.20752984e+05, 6.36771758e+05,
       6.52994592e+05, 6.69421488e+05, 6.86052444e+05, 7.02887460e+05,
       7.19926538e+05, 7.37169677e+05, 7.54616876e+05, 7.72268136e+05,
       7.90123457e+05, 8.08182838e+05, 8.26446281e+05, 8.44913784e+05,
       8.63585348e+05, 8.82460973e+05, 9.01540659e+05, 9.20824406e+05,
       9.40312213e+05, 9.60004081e+05, 9.79900010e+05, 1.00000000e+06])

Подробнее этот пример мы рассмотрим позже, когда будем работать с matplotlib.

4.Еще numpy можно использовать в статистике. Например, чтобы посчитать выборочную дисперсию, стандартное отклонение, медиану. Важный момент: здесь numpy чем-то похож на R, он не сможет выдать результат в случае, если в массиве присутствуют пропущенные значения. Проверим.

In [81]:
q = np.array([1., 0., 4.5, np.nan, 3.]) # np.nan - Not a number (пропущенное значение)
q
Out[81]:
array([1. , 0. , 4.5, nan, 3. ])
In [85]:
np.var(q) # получаем nan вместо дисперсии
Out[85]:
nan
In [84]:
np.nanvar(q) # если функция начинается с nan, все работает
Out[84]:
3.046875

Аналогичная история с медианой:

In [86]:
np.median(q)
/usr/local/lib/python3.5/dist-packages/numpy/lib/function_base.py:4033: RuntimeWarning: Invalid value encountered in median
  r = func(a, **kwargs)
Out[86]:
nan
In [87]:
np.nanmedian(q)
Out[87]:
2.0

Еще с помощью numpy можно считать выборочный коэффициент корреляции Пирсона. Функция corrcoef() возвращает корреляционную матрицу, из которой можно извлечь коэффициент линейной корреляции.

In [88]:
x = np.array([2, 6, 8, 10, 12])
y = np.array([4, 7, 14, 21, 19])

np.corrcoef(x, y)
Out[88]:
array([[1.        , 0.93307545],
       [0.93307545, 1.        ]])
In [89]:
np.corrcoef(x, y)[0][1]
Out[89]:
0.9330754546745095

Внимание: не путайте функцию corrcoef() с correlate()!

In [90]:
np.correlate(x, y)
Out[90]:
array([600])

Функция correlate() используется для нахождения кросс-корреляции.

In [91]:
help(np.correlate)
Help on function correlate in module numpy.core.numeric:

correlate(a, v, mode='valid')
    Cross-correlation of two 1-dimensional sequences.
    
    This function computes the correlation as generally defined in signal
    processing texts::
    
        c_{av}[k] = sum_n a[n+k] * conj(v[n])
    
    with a and v sequences being zero-padded where necessary and conj being
    the conjugate.
    
    Parameters
    ----------
    a, v : array_like
        Input sequences.
    mode : {'valid', 'same', 'full'}, optional
        Refer to the `convolve` docstring.  Note that the default
        is 'valid', unlike `convolve`, which uses 'full'.
    old_behavior : bool
        `old_behavior` was removed in NumPy 1.10. If you need the old
        behavior, use `multiarray.correlate`.
    
    Returns
    -------
    out : ndarray
        Discrete cross-correlation of `a` and `v`.
    
    See Also
    --------
    convolve : Discrete, linear convolution of two one-dimensional sequences.
    multiarray.correlate : Old, no conjugate, version of correlate.
    
    Notes
    -----
    The definition of correlation above is not unique and sometimes correlation
    may be defined differently. Another common definition is::
    
        c'_{av}[k] = sum_n a[n] conj(v[n+k])
    
    which is related to ``c_{av}[k]`` by ``c'_{av}[k] = c_{av}[-k]``.
    
    Examples
    --------
    >>> np.correlate([1, 2, 3], [0, 1, 0.5])
    array([ 3.5])
    >>> np.correlate([1, 2, 3], [0, 1, 0.5], "same")
    array([ 2. ,  3.5,  3. ])
    >>> np.correlate([1, 2, 3], [0, 1, 0.5], "full")
    array([ 0.5,  2. ,  3.5,  3. ,  0. ])
    
    Using complex sequences:
    
    >>> np.correlate([1+1j, 2, 3-1j], [0, 1, 0.5j], 'full')
    array([ 0.5-0.5j,  1.0+0.j ,  1.5-1.5j,  3.0-1.j ,  0.0+0.j ])
    
    Note that you get the time reversed, complex conjugated result
    when the two input sequences change places, i.e.,
    ``c_{va}[k] = c^{*}_{av}[-k]``:
    
    >>> np.correlate([0, 1, 0.5j], [1+1j, 2, 3-1j], 'full')
    array([ 0.0+0.j ,  3.0+1.j ,  1.5+1.5j,  1.0+0.j ,  0.5+0.5j])