Введение в Numpy и Pandas

Введение в NumPy и Pandas

 

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

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

Когда вы начинаете свой путь в науке о данных, рекомендуется начать с изучения двух наиболее полезных пакетов Python: NumPy и Pandas. В этой статье мы представляем эти две библиотеки. Давайте начнем!

 

Что такое NumPy?

 

NumPy расшифровывается как Numerical Python и используется для эффективных вычислений массивов и матриц в основе моделей машинного обучения. Основным строительным блоком NumPy является массив, который является структурой данных, очень похожей на список, с той разницей, что он предоставляет огромное количество математических функций. Другими словами, массив NumPy является многомерным массивным объектом. 

 

Создание массивов NumPy

 

Мы можем определить массивы NumPy, используя список или список списков:

import numpy as np
l = [[1,2,3],[4,5,6],[7,8,9]]
numpy_array = np.array(l)
numpy_array

 

array([[1, 2, 3],
       [4, 5, 6],
       [7, 8, 9]])

 

В отличие от списка списков, мы можем визуализировать матрицу 3X3 с отступом между каждой строкой. Кроме того, NumPy предоставляет более 40 встроенных функций для создания массивов. 

Чтобы создать массив, заполненный нулями, есть функция np.zeros, в которой нужно указать желаемую форму:

zeros_array = np.zeros((3,4))
zeros_array

 

array([[0., 0., 0., 0.],
       [0., 0., 0., 0.],
       [0., 0., 0., 0.]])

 

Аналогичным образом мы можем создать массив, заполненный единицами:

ones_array = np.ones((3,4))
ones_array

 

array([[1., 1., 1., 1.],
       [1., 1., 1., 1.],
       [1., 1., 1., 1.]])

 

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

identity_array = np.identity(3)
identity_array

 

array([[1., 0., 0.],
       [0., 1., 0.],
       [0., 0., 1.]])

 

Кроме того, NumPy предоставляет различные функции для создания случайных массивов. Чтобы создать массив, заполненный случайными выборками из равномерного распределения на интервале [0,1], нам просто нужна функция np.random.rand:

random_array = np.random.rand(3,4)
random_array

 

array([[0.84449279, 0.71146992, 0.48159787, 0.04927379],
       [0.03428534, 0.26851667, 0.65718662, 0.52284251],
       [0.1380207 , 0.91146148, 0.74171469, 0.57325424]])

 

Аналогично предыдущей функции, мы можем определить массив со случайными значениями, но на этот раз они берутся из стандартного нормального распределения:

randn_array = np.random.randn(10)
randn_array

 

array([-0.68398432, -0.25466784,  0.27020797,  0.29632334, -0.20064897,
        0.7988508 ,  1.34759319, -0.41418478, -0.35223377, -0.10282884])

 

В случае, если нам интересно построить массив со случайными целыми числами, принадлежащими интервалу [нижнее, верхнее), нам просто нужна функция np.random.randint :

randint_array = np.random.randint(1,20,20)
randint_array

 

array([14,  3,  1,  2, 17, 15,  5, 17, 18,  9,  4, 19, 14, 14,  1, 10, 17,
       19,  4,  6])

 

Индексирование и срезы

 

Помимо встроенных функций для создания массива, еще одним хорошим моментом в NumPy является то, что с помощью квадратных скобок можно выбирать элементы из массива. Например, мы можем попробовать взять первую строку матрицы:

a1 = np.array([[1,2,3],[4,5,6]])
a1[0]

 

array([1, 2, 3])

 

Допустим, нам нужно выбрать третий элемент из первой строки. В этом случае нам нужно указать два индекса, индекс строки и индекс столбца:

print(a1[0,2]) #3

 

Альтернативой является использование a1[0][2], но это считается неэффективным, потому что сначала создается массив, содержащий первую строку, а затем выбирается элемент из этой строки.

Кроме того, мы можем брать срезы из матрицы с помощью синтаксиса start:stop:step внутри квадратных скобок, где индекс stop не включается. Например, мы снова хотим выбрать первую строку, но берем только первые два элемента:

print(a1[0,0:2]) 

 

[1 2]

 

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

print(a1[:,0])

 

[1 4]

 

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

a1>5

 

array([[False, False, False],
       [False, False,  True]])

 

Если мы отфильтруем массив на основе этого условия, результатом будет только True элементы:

a1[a1>5]

 

array([6])

 

Манипуляция с массивами

 

При работе над проектами в области науки о данных часто возникает необходимость изменить форму массива на новую форму, не изменяя данные. 

Например, мы начинаем с массива размерностью 2X3. Если мы не уверены в форме нашего массива, есть атрибут shape, который может нам помочь:

a1 = np.array([[1,2,3],[4,5,6]])
print(a1)
print('Форма массива: ',a1.shape)

 

[[1 2 3]
 [4 5 6]]
Форма массива:  (2, 3)

 

Чтобы изменить форму массива на размерность 3X2, мы можем просто использовать функцию reshape:

a1 = a1.reshape(3,2)
print(a1)
print('Форма массива: ',a1.shape)

 

[[1 2]
 [3 4]
 [5 6]]
Форма массива:  (3, 2)

 

Еще одна обычная ситуация – превратить многомерный массив в одномерный массив. Это возможно, задав -1 в качестве формы:

a1 = a1.reshape(-1)
print(a1)
print('Форма массива: ',a1.shape)

 

[1 2 3 4 5 6]
Форма массива:  (6,)

 

Также может возникнуть необходимость получить транспонированный массив:

a1 = np.array([[1,2,3,4,5,6]])
print('Исходная форма массива: ',a1.shape)
a1 = a1.T
print(a1)
print('Форма массива после транспонирования: ',a1.shape)

 

Исходная форма массива:  (1, 6)
[[1]
 [2]
 [3]
 [4]
 [5]
 [6]]
Форма массива после транспонирования:  (6, 1)

 

Точно так же вы можете применить ту же трансформацию, используя np.transpose(a1). 

 

Умножение массивов

 

Если вы попытаетесь разработать алгоритмы машинного обучения с нуля, вам неизбежно придется вычислить произведение матриц двух массивов. Это возможно с помощью функции np.matmul, когда массив имеет более одного измерения:

a1 = np.array([[1,2,3],[4,5,6]])
a2 = np.array([[1,2],[4,5],[7,8]])
print('Форма массива a1: ',a1.shape)
print('Форма массива a2: ',a2.shape)
a3 = np.matmul(a1,a2) 
# a3 = a1 @ a2
print(a3)
print('Форма массива a3: ',a3.shape)

 

Форма массива a1:  (2, 3)
Форма массива a2:  (3, 2)
[[30 36]
 [66 81]]
Форма массива a3:  (2, 2)

 

@ может быть более короткой альтернативой для np.matmul. 

Если вы умножаете матрицу на скаляр, лучший выбор – использовать np.dot:

a1 = np.array([[1,2,3],[4,5,6]])
a3 = np.dot(a1,2)
# a3 = a1 * 2
print(a3)
print('Форма массива a3: ',a3.shape)

 

[[ 2  4  6]
 [ 8 10 12]]
Форма массива a3:  (2, 3)

 

В этом случае * является более короткой альтернативой для np.dot. 

 

Математические функции

 

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

Экспонента и натуральный логарифм являются, безусловно, самыми популярными и известными преобразованиями:

a1 = np.array([[1,2,3],[4,5,6]])
print(np.exp(a1))

 

[[  2.71828183   7.3890561   20.08553692]
 [ 54.59815003 148.4131591  403.42879349]]

 

a1 = np.array([[1,2,3],[4,5,6]])
print(np.log(a1))

 

[[0.         0.69314718 1.09861229]
 [1.38629436 1.60943791 1.79175947]]

 

Если мы хотим извлечь минимум и максимум в одной строке кода, нам просто нужно вызвать следующие функции:

a1 = np.array([[1,2,3],[4,5,6]])
print(np.min(a1),np.max(a1))  # 1 6

 

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

a1 = np.array([[1,2,3],[4,5,6]])
print(np.sqrt(a1))

 

[[1.         1.41421356 1.73205081]
 [2.         2.23606798 2.44948974]]

 

Что такое Pandas?

 

Pandas создан на основе Numpy и полезен для манипулирования набором данных. Существуют две основные структуры данных: Series и Dataframe. В то время как Series представляет собой последовательность значений, Dataframe представляет собой таблицу с рядами и столбцами. Другими словами, Series является столбцом Dataframe.

 

Создание Series и Dataframe

 

Для создания Series мы можем просто передать список значений в метод:

import pandas as pd
type_house = pd.Series(['Loft','Villa'])
type_house

 

0     Loft
1    Villa
dtype: object

 

Мы можем создать Dataframe, передав словарь объектов, в котором ключи соответствуют именам столбцов, а значения – записям столбцов:

df = pd.DataFrame({'Price': [100000, 300000], 'date_construction': [1960, 2010]})
df.head()

 

 

После создания Dataframe мы можем проверить тип каждого столбца:

type(df.Price),type(df.date_construction)

 

(pandas.core.series.Series, pandas.core.series.Series)

 

Ясно, что столбцы являются структурами данных типа Series.

 

Функции сводки

 

С этого момента мы покажем возможности Pandas, используя набор данных о велосипедах, доступных на Kaggle. Мы можем импортировать файл CSV следующим образом:

df = pd.read_csv('/kaggle/input/bike-sharing-demand/train.csv')
df.head()

 

 

Pandas позволяет не только читать файлы CSV, но и файлы Excel, JSON, Parquet и другие типы файлов. Полный список можно найти здесь.

Из вывода мы можем визуализировать первые пять строк набора данных. Если мы хотим отобразить последние четыре строки набора данных, мы используем метод tail():

df.tail(4)

 

 

Несколько строк недостаточно, чтобы иметь хорошее представление о данных, которыми мы располагаем. Хорошим способом начать анализ является просмотр формы набора данных:

df.shape                    #(10886, 12)

 

У нас есть 10886 строк и 12 столбцов. Хотите увидеть названия столбцов? Это очень интуитивно:

df.columns

 

 

Существует метод, который позволяет визуализировать всю эту информацию в одном выводе:

df.info()

 

 

Если мы хотим отобразить статистику каждого столбца, мы можем использовать метод describe:

df.describe()

 

 

Также важно извлекать информацию из категориальных полей. Мы можем найти уникальные значения и количество уникальных значений столбца season:

df.season.unique(),df.season.nunique()

 

Вывод:

(array([1, 2, 3, 4]), 4)

 

Мы видим, что значения равны 1, 2, 3, 4. Значит, имеются четыре возможных значения. Эта проверка важна для понимания категориальных переменных и предотвращения возможного шума, содержащегося в столбце.

Чтобы отобразить частоту каждого уровня, мы можем использовать метод value_counts():

df.season.value_counts()

 

 

Последним шагом должна быть проверка отсутствующих значений в каждом столбце:

df.isnull().sum()

 

 

К счастью, у нас нет отсутствующих значений ни в одном из этих полей.

 

Индексация и срез

 

Как и в Numpy, существует индексный выбор для выбора данных из структуры данных. Есть два основных метода для выбора элементов из фрейма данных:

  • iloc выбирает элементы на основе целочисленной позиции
  • loc берет элементы на основе меток или булевого массива.

Для выбора первой строки лучше всего использовать iloc:

df.iloc[0]

 

 

Если мы хотим выбрать все строки и только второй столбец, мы можем сделать следующее:

df.iloc[:,1]

 

 

Также можно выбрать несколько столбцов одновременно:

df.iloc[0:3,[0,1,2,5]]

 

 

Становится сложно выбирать столбцы на основе индексов. Было бы лучше указывать имена столбцов. Это возможно с использованием loc:

df.loc[0:3,['datetime','season','holiday','temp']]

 

 

Аналогично Numpy, можно фильтровать фрейм данных на основе условий. Например, мы хотим вернуть все строки, где weather равно 1:

df[df.weather==1]

 

 

Если мы хотим вернуть вывод с определенными столбцами, мы можем использовать loc:

df.loc[df.weather==1,['season','holiday']]

 

 

Создание новых переменных

 

Создание новых переменных имеет огромное значение для извлечения большего количества информации из данных и улучшения их интерпретируемости. Мы можем создать новую категориальную переменную на основе значений переменной “workingday”:

df['workingday_c'] = df['workingday'].apply(lambda x: 'work' if x==1 else 'relax')
df[['workingday','workingday_c']].head()

 

 

Если условий больше одного, лучше использовать словарь и метод map для преобразования значений:

diz_season = {1:'winter',2:'spring',3:'summer',4:'fall'}
df['season_c'] = df['season'].map(lambda x: diz_season[x])
df[['season','season_c']].head()

 

 

Группировка и сортировка

 

Может возникнуть ситуация, когда необходимо сгруппировать данные на основе категориальных столбцов. Это возможно с помощью функции groupby:

df.groupby('season_c').agg({'count':['median','max']})

 

 

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

df.groupby('season_c').agg({'count':['median','max']}).reset_index().sort_values(by=('count', 'median'),ascending=False)

 

 

Теперь вывод имеет более ясный смысл. Мы можем сделать вывод, что наибольшее количество арендованных велосипедов приходится на лето, в то время как зима не является хорошим месяцем для аренды велосипедов. 

 

Заключительные замечания

 

Вот и всё! Надеюсь, этот руководство было полезным для изучения основ NumPy и Pandas. Они часто изучаются отдельно, но полезно сначала понять NumPy, а затем Pandas, который построен на основе NumPy. 

В учебнике наверняка есть методы, которые я не рассмотрел, но целью было осветить наиболее важные и популярные методы этих двух библиотек. Код можно найти на Kaggle. Спасибо за чтение! Хорошего дня!     Евгения Анелло в настоящее время является научным сотрудником в отделе инженерии информации Университета Падуи, Италия. Ее исследовательский проект сосредоточен на непрерывном обучении в сочетании с обнаружением аномалий.