Pandas python руководство

Пакет pandas — это самый важный инструмент из арсенала специалистов по Data Science и аналитиков, работающих на Python. Мощные инструменты машинного обучения и блестящие средства визуализации могут привлекать внимание, но в основе большинства проектов по работе с данными лежит pandas.

Название pandas происходит от термина «панельные данные» (англ. panel data). В эконометрии это многомерные структурированные наборы данных. (Источник — Википедия).

Если вы собираетесь построить карьеру в сфере науки о данных, то одним из первых ваших действий должно стать изучение этой библиотеки. В этой статье мы изложим основные сведения о pandas. Вы узнаете, как установить этот пакет и как его использовать. Кроме того, мы рассмотрим, как pandas работает с другими популярными пакетами для анализа данных Python, такими как matplotlib и scikit-learn.

Содержание

  • Для чего нужна библиотека pandas?
  • Какое место занимает pandas в наборе инструментов Data Science?
  • Когда стоит начать использовать pandas?
  • Установка и импорт pandas
  • Основные компоненты pandas: Series и DataFrame
    • Создание DataFrame pandas с нуля
  • Как считывать данные в DataFrame pandas
    • Чтение данных из CSV-файлов
    • Чтение данных из JSON
    • Чтение данных из базы данных SQL
    • Преобразование данных обратно в CSV, JSON или SQL
  • Наиболее важные операции с DataFrame
    • Просмотр данных
    • Получение информации о ваших данных
    • Обработка дубликатов
    • Очистка столбцов
    • Обработка отсутствующих значений
    • Понимание ваших переменных
    • Отношения между непрерывными переменными
    • Срезы, выборка, извлечение DataFrame
    • Применение функций
    • Кратко о графиках

У pandas так много применений, так что лучше перечислить не ее возможности, а то, что она не может делать.

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

Допустим, вы хотите изучить набор данных, хранящийся в CSV-формате на вашем компьютере. Pandas извлечет данные из этого CSV в DataFrame — по сути, таблицу, — а затем позволит вам сделать следующее:

  • Рассчитать статистику и ответить на вопросы о данных, такие как:
    • Каково среднее значение, медиана, максимум или минимум для каждого столбца?
    • Коррелирует ли столбец A со столбцом B?
    • Как выглядит распределение данных в столбце C?
  • Очистить данные, например, удалив пропущенные значения или отфильтровав строки или столбцы по каким-либо критериям.
  • Визуализировать данные с помощью Matplotlib. Можно построить столбчатые диаграммы, линейные графики, гистограммы.
  • Сохранить очищенные, преобразованные данные в CSV и другие форматы файлов или в базе данных.

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

Библиотека pandas не только является центральным компонентом набора инструментов для науки о данных, но и используется в сочетании с другими библиотеками из этого набора.

Pandas построена на основе пакета NumPy, то есть многие структуры NumPy используются или воспроизводятся в pandas. Данные в pandas часто используются для статистического анализа в SciPy, в функциях построения графиков из Matplotlib и в алгоритмах машинного обучения в Scikit-learn.

Jupyter Notebook предлагает хорошую среду для использования pandas для исследования и моделирования данных, но pandas также легко можно использовать в текстовых редакторах.

Jupyter Notebook дает нам возможность выполнять код в конкретной ячейке, а не запускать весь файл. Это экономит много времени при работе с большими наборами данных и сложными преобразованиями. Блокноты также предоставляют простой способ визуализации pandas DataFrame и графиков. Собственно говоря, эта статья была полностью создана в Jupyter Notebook.

Когда стоит начать использовать pandas?

Если у вас нет опыта программирования на Python, то вам лучше пока воздержаться от изучения pandas. Уровень опытного разработчика не обязателен, но вы должны знать основы работы с Python (списки, кортежи, словари, функции, итерации и т. п. вещи). Также желательно сперва познакомиться с NumPy из-за сходства, упомянутого выше.

Тем, кто собирается пройти обучение на курсах по data science, настоятельно рекомендуется начать изучать pandas самостоятельно еще до прохождения курса.

Даже если в программе курса будет обучение pandas, заранее приобретенные навыки позволят вам максимально эффективно использовать время для изучения и освоения более сложного материала.

Установка и импорт pandas

Pandas — это простой в установке пакет. Откройте терминал или командную строку и установите пакет с помощью одной из следующих команд:

conda install pandas

ИЛИ

pip install pandas

Также, если вы просматриваете эту статью в Jupyter Notebook, можно запустить эту ячейку:

!pip install pandas

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

При импорте pandas обычно используется более короткое имя пакета, поскольку набирать его придется очень часто:

import pandas as pd

Переходим к основным компонентам pandas.

Основные компоненты pandas: Series и DataFrame

Двумя основными компонентами pandas являются Series и DataFrame.

Series — это, по сути, столбец, а DataFrame — это многомерная таблица, состоящая из набора Series.

Табличное представление Series и DataFrame pandas. Сложение двух Series дает DataFrame/

DataFrame и Series очень похожи, поскольку многие операции, которые вы можете выполнять с одним из них, можно выполнять и с другим, например, заполнение нулевых значений и вычисление среднего значения.

Вы увидите эти компоненты в действии, когда мы начнем работать с данными.

Создание DataFrame pandas с нуля

Умение создавать DataFrame непосредственно на Python — полезный навык. Он пригодится вам при тестировании новых методов и функций, найденных в документации pandas.

Существует множество способов создания DataFrame с нуля, но отличным вариантом является использование простого словаря.

Допустим, у нас есть фруктовый киоск, где продаются яблоки и апельсины. Мы хотим иметь столбец для каждого фрукта и строку для каждой покупки клиента. Чтобы организовать это в виде словаря для pandas, мы можем сделать так:

data = {
    'apples': [3, 2, 0, 1], 
    'oranges': [0, 3, 7, 2]
}

И затем передать data в конструктор DataFrame:

purchases = pd.DataFrame(data)

purchases
apples oranges
0 3 0
1 2 3
2 0 7
3 1 2

Как это работает?

Каждый элемент ключ-значение в данных соответствует столбцу в полученном DataFrame.

Индекс этого DataFrame был задан при создании самого DataFrame в виде чисел 0-3. Но индекс также можно создать самостоятельно при инициализации DataFrame.

Пусть в качестве индекса будут имена клиентов:

purchases = pd.DataFrame(data, index=['June', 'Robert', 'Lily', 'David'])

purchases
apples oranges
June 3 0
Robert 2 3
Lily 0 7
David 1 2

Теперь мы можем найти заказ клиента по его имени.

purchases.loc['June']

Примечание переводчика: loc — сокращение от locate, что в переводе с английского означает «определять местонахождение».

Результат:

apples     3
oranges    0
Name: June, dtype: int64

Более подробно о поиске и извлечении данных из DataFrame мы поговорим позже. Сейчас сосредоточимся на создании DataFrame с любыми произвольными данными. Давайте перейдем к быстрым методам создания DataFrame из различных источников.

Как считывать данные в DataFrame pandas

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

Чтение данных из CSV-файлов

Для загрузки данных из CSV-файлов достаточно одной строки:

df = pd.read_csv('purchases.csv')

df
Unnamed: 0 apples oranges
0 June 3 0
1 Robert 2 3
2 Lily 0 7
3 David 1 2

CSV не имеют индексов, как наши DataFrames, поэтому все, что нам нужно сделать, это просто определить index_col при чтении:

df = pd.read_csv('purchases.csv', index_col=0)

df
apples oranges
June 3 0
Robert 2 3
Lily 0 7
David 1 2

Здесь мы устанавливаем, что индексом будет нулевой столбец.

Большинство CSV не имеют индексного столбца, поэтому обычно вам не нужно беспокоиться об этом шаге.

Чтение данных из JSON

Если у вас есть файл JSON, который по сути является хранимым словарем Python, pandas может прочитать его так же легко:

df = pd.read_json('purchases.json')

df
apples oranges
David 1 2
June 3 0
Lily 0 7
Robert 2 3

Обратите внимание, что на этот раз мы получили правильный индекс, поскольку использование JSON позволяет индексам работать через вложенность. Попробуйте открыть файл data_file.json в блокноте, чтобы посмотреть, как он работает.

Pandas пытается понять, как создать DataFrame, анализируя структуру вашего JSON, и иногда у него это не получается. Вам часто придется задавать именованный аргумент orient, зависящий от формата строки JSON. Почитать об этом аргументе можно в документации read_json.

От редакции Pythonist: также предлагаем почитать статью «Работа с большими данными в Python при помощи Pandas и JSON».

Чтение данных из базы данных SQL

Если вы работаете с данными из базы данных SQL, вам нужно сначала установить соединение с помощью соответствующей библиотеки Python, а затем передать запрос в pandas. Для демонстрации мы будем использовать SQLite.

Для начала нужно установить pysqlite3. Выполните следующую команду в терминале:

pip install pysqlite3

Или запустите эту ячейку, если находитесь в Jupyter Notebook:

!pip install pysqlite3

sqlite3 используется для соединения с базой данных, которую мы затем можем использовать для создания DataFrame с помощью запроса SELECT.

Итак, сначала мы создадим соединение с файлом базы данных SQLite:

import sqlite3

con = sqlite3.connect("database.db")

Совет относительно SQL

Если у вас данные хранятся на PostgreSQL, MySQL или другом SQL-сервере, вам нужно использовать правильную библиотеку Python для установки соединения. Например, для соединения с PostgreSQL часто используется psycopg2. Кроме того, вы можете соединяться с URI базы данных, а не с файлом, как в нашем примере с SQLite.


В этой базе данных SQLite у нас есть таблица под названием purchases, а наш индекс — столбец index.

Передав запрос SELECT и наш con, мы можем читать из таблицы purchases:

df = pd.read_sql_query("SELECT * FROM purchases", con)

df
apples oranges
0 June 3 0
1 Robert 2 3
2 Lily 0 7
3 David 1 2

Как и в случае с CSV, мы можем передать index_col='index', но мы также можем установить индекс постфактум:

df = df.set_index('index')

df
apples oranges
index
June 3 0
Robert 2 3
Lily 0 7
David 1 2

Фактически, мы в любое время можем использовать set_index() для любого DataFrame, используя любой столбец. Индексирование Series и DataFrame — очень распространенная задача, и стоит помнить о различных способах ее выполнения.

Преобразование данных обратно в CSV, JSON или SQL

Итак, после длительной работы по очистке данных вы готовы сохранить их в файл. Pandas предоставляет интуитивно понятные команды не только для чтения, но и для сохранения данных:

df.to_csv('new_purchases.csv')

df.to_json('new_purchases.json')

df.to_sql('new_purchases', con)

От редакции Pythonist: также предлагаем почитать статью «Как очистить данные при помощи Pandas».

Когда мы сохраняем файлы JSON и CSV, все, что нам нужно передать в эти функции, — это желаемое имя файла с соответствующим расширением. При использовании SQL мы не создаем новый файл, а вставляем новую таблицу в базу данных, используя нашу переменную con.

А теперь давайте перейдем к импорту реальных данных и подробно рассмотрим несколько операций, которые вы будете часто использовать.

Наиболее важные операции с DataFrame

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

Для начала загрузим набор данных, касающихся фильмов IMDB:

movies_df = pd.read_csv("IMDB-Movie-Data.csv", index_col="Title")

Мы загружаем этот набор данных из CSV и определяем названия фильмов нашим индексом.

Просмотр данных

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

movies_df.head()
Rank Genre Description Director Actors Year Runtime (Minutes) Rating Votes Revenue (Millions) Metascore
Title
Guardians of the Galaxy 1 Action,Adventure,Sci-Fi A group of intergalactic criminals are forced … James Gunn Chris Pratt, Vin Diesel, Bradley Cooper, Zoe S… 2014 121 8.1 757074 333.13 76.0
Prometheus 2 Adventure,Mystery,Sci-Fi Following clues to the origin of mankind, a te… Ridley Scott Noomi Rapace, Logan Marshall-Green, Michael Fa… 2012 124 7.0 485820 126.46 65.0
Split 3 Horror,Thriller Three girls are kidnapped by a man with a diag… M. Night Shyamalan James McAvoy, Anya Taylor-Joy, Haley Lu Richar… 2016 117 7.3 157606 138.12 62.0
Sing 4 Animation,Comedy,Family In a city of humanoid animals, a hustling thea… Christophe Lourdelet Matthew McConaughey,Reese Witherspoon, Seth Ma… 2016 108 7.2 60545 270.32 59.0
Suicide Squad 5 Action,Adventure,Fantasy A secret government agency recruits some of th… David Ayer Will Smith, Jared Leto, Margot Robbie, Viola D… 2016 123 6.2 393727 325.02 40.0

.head() по умолчанию выводит первые пять строк вашего DataFrame, но можно передать и необходимое число: например, movies_df.head(10) выведет десять верхних строк.

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

movies_df.tail(2)
Rank Genre Description Director Actors Year Runtime (Minutes) Rating Votes Revenue (Millions) Metascore
Title
Search Party 999 Adventure,Comedy A pair of friends embark on a mission to reuni… Scot Armstrong Adam Pally, T.J. Miller, Thomas Middleditch,Sh… 2014 93 5.6 4881 NaN 22.0
Nine Lives 1000 Comedy,Family,Fantasy A stuffy businessman finds himself trapped ins… Barry Sonnenfeld Kevin Spacey, Jennifer Garner, Robbie Amell,Ch… 2016 87 5.3 12435 19.64 11.0

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

Индекс в нашем DataFrame — это столбец Title. Это видно по тому, что слово Title расположено немного ниже, чем остальные столбцы.

Получение информации о ваших данных

Одной из первых команд, которые выполняются после загрузки данных, является .info():

movies_df.info()

Результат:

<class 'pandas.core.frame.DataFrame'>
Index: 1000 entries, Guardians of the Galaxy to Nine Lives
Data columns (total 11 columns):
Rank                  1000 non-null int64
Genre                 1000 non-null object
Description           1000 non-null object
Director              1000 non-null object
Actors                1000 non-null object
Year                  1000 non-null int64
Runtime (Minutes)     1000 non-null int64
Rating                1000 non-null float64
Votes                 1000 non-null int64
Revenue (Millions)    872 non-null float64
Metascore             936 non-null float64
dtypes: float64(3), int64(4), object(4)
memory usage: 93.8+ KB

.info() предоставляет основные сведения о вашем наборе данных, такие как количество строк и столбцов, количество ненулевых значений, тип данных в каждом столбце и объем памяти, используемый вашим DataFrame.

Обратите внимание, что в нашем наборе данных фильмов явно есть несколько отсутствующих значений в столбцах Revenue и Metascore. Мы рассмотрим, как их обработать.

Быстрый просмотр типа данных очень полезен. Представьте, что вы только что импортировали JSON, и целые числа были записаны как строки. Вы собираетесь произвести некоторые арифметические действия и натыкаетесь на исключение «unsupported operand», потому что производить математические операции со строками нельзя. Вызов .info() быстро покажет, что столбец, который вы считали целыми числами, на самом деле является строковым объектом.

Еще один быстрый и полезный атрибут — .shape. Он выводит просто кортеж (строки, столбцы):

movies_df.shape

# Результат: 
# (1000, 11)

Итак, в нашем DataFrame movies есть 1000 строк и 11 столбцов. Обратите внимание, что .shape не имеет круглых скобок и является простым кортежем в формате (строки, столбцы).

Вы будете часто обращаться к .shape при очистке и преобразовании данных. Например, с его помощью можно отфильтровать некоторые строки по каким-либо критериям, а затем быстро узнать, сколько строк было удалено.

Обработка дубликатов

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

Чтобы продемонстрировать это, давайте просто удвоим наш DataFrame movies, добавив его к самому себе:

temp_df = movies_df.append(movies_df)

temp_df.shape

# Результат:
# (2000, 11)

Использование append() вернет копию, не затрагивая исходный DataFrame. Мы захватываем эту копию в temp, так что не будем работать с реальными данными.

Вызов .shape быстро доказывает, что строки нашего DataFrame удвоились.

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

temp_df = temp_df.drop_duplicates()

temp_df.shape

# Результат: 
# (1000, 11)

Как и append(), метод drop_duplicates() также вернет копию вашего DataFrame, но на этот раз с удаленными дубликатами. Вызов .shape подтверждает, что мы вернулись к 1000 строкам нашего исходного набора данных.

Продолжать присваивать DataFrame одной и той же переменной, как в этом примере, немного многословно. Поэтому у pandas есть ключевое слово inplace во многих методах. Использование inplace=True изменит исходный объект DataFrame:

temp_df.drop_duplicates(inplace=True)

Теперь наш temp_df будет автоматически содержать преобразованные данные.

Еще один важный аргумент drop_duplicates()keep, который имеет три возможных опции:

  • first: Отбросить дубликаты, кроме первого вхождения (опция по умолчанию).
  • last: Отбросить дубликаты, кроме последнего вхождения.
  • False: Отбросить все дубликаты.

Поскольку в предыдущем примере мы не определили аргумент keep, он был задан по умолчанию как first. Это означает, что если две строки одинаковы, pandas отбросит вторую и сохранит первую. Использование last имеет противоположный эффект: отбрасывается первая строка.

А вот keep=False отбрасывает все дубликаты. Если две строки одинаковы, то обе будут отброшены. Посмотрите, что произойдет с temp_df:

temp_df = movies_df.append(movies_df)  # make a new copy

temp_df.drop_duplicates(inplace=True, keep=False)

temp_df.shape

# Результат:
# (0, 11)

Поскольку все строки были дубликатами, keep=False отбрасывает их все, в результате чего остается ноль строк. Если вы задаетесь вопросом, зачем это нужно, то одна из причин заключается в том, что это позволяет найти все дубликаты в наборе данных.

Очистка столбцов

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

Вот как вывести имена столбцов нашего набора данных:

movies_df.columns

Результат:

Index(['Rank', 'Genre', 'Description', 'Director', 'Actors', 'Year',
       'Runtime (Minutes)', 'Rating', 'Votes', 'Revenue (Millions)',
       'Metascore'],
      dtype='object')

Функция .columns пригодится не только для переименования столбцов, но и в том случае, если вам нужно понять, почему вы получаете ошибку Key Error при выборе данных по столбцам.

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

movies_df.rename(columns={
        'Runtime (Minutes)': 'Runtime', 
        'Revenue (Millions)': 'Revenue_millions'
    }, inplace=True)


movies_df.columns

Результат:

Index(['Rank', 'Genre', 'Description', 'Director', 'Actors', 'Year', 'Runtime',
       'Rating', 'Votes', 'Revenue_millions', 'Metascore'],
      dtype='object')

Отлично. Но что, если мы хотим перевести все названия в нижний регистр? Вместо того чтобы использовать .rename(), мы могли бы задать список названий столбцов, например, так:

movies_df.columns = ['rank', 'genre', 'description', 'director', 'actors', 'year', 'runtime', 
                     'rating', 'votes', 'revenue_millions', 'metascore']


movies_df.columns

Результат:

Index(['rank', 'genre', 'description', 'director', 'actors', 'year', 'runtime',
       'rating', 'votes', 'revenue_millions', 'metascore'],
      dtype='object')

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

movies_df.columns = [col.lower() for col in movies_df]

movies_df.columns

Результат:

Index(['rank', 'genre', 'description', 'director', 'actors', 'year', 'runtime',
       'rating', 'votes', 'revenue_millions', 'metascore'],
      dtype='object')

Представления списков и словарей вообще часто пригождаются при работе с pandas и данными в целом.

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

Обработка отсутствующих значений

При исследовании данных вы, скорее всего, столкнетесь с отсутствующими значениями или null, которые, по сути, являются заглушками для несуществующих значений. Чаще всего вы увидите None в Python или np.nan в NumPy.

Есть два варианта работы с null:

  1. Избавиться от строк или столбцов с нулевыми значениями
  2. Заменить нулевые значения на ненулевые — метод, известный как импутация.

Давайте подсчитаем общее количество null в каждом столбце нашего набора данных. Первым шагом будет проверка того, какие ячейки в нашем DataFrame являются нулевыми:

movies_df.isnull()
rank genre description director actors year runtime rating votes revenue_millions metascore
Title
Guardians of the Galaxy False False False False False False False False False False False
Prometheus False False False False False False False False False False False
Split False False False False False False False False False False False
Sing False False False False False False False False False False False
Suicide Squad False False False False False False False False False False False

Обратите внимание, что isnull() возвращает DataFrame, в котором каждая ячейка либо True, либо False, в зависимости от null-статуса этой ячейки.

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

movies_df.isnull().sum()

Результат:

rank                  0
genre                 0
description           0
director              0
actors                0
year                  0
runtime               0
rating                0
votes                 0
revenue_millions    128
metascore            64
dtype: int64

.isnull() сама по себе не очень полезна и обычно используется в сочетании с другими методами, например sum().

Теперь мы знаем, что наши данные имеют 128 отсутствующих значений для revenue_millions и 64 отсутствующих значения для metascore.

Удаление нулевых значений

Специалистам по Data Science и аналитикам регулярно приходится выбирать, стоит ли удалить или импутировать значения null. Подобное решение требует глубокого знания ваших данных и их контекста. В целом, удаление нулевых данных рекомендуется только в том случае, если у вас небольшой объем отсутствующих данных.

Удалить значения null довольно просто:

movies_df.dropna()

Эта операция удалит любую строку, содержащую хотя бы одно нулевое значение, но вернет новый DataFrame без изменения исходного. В этом методе также можно указать inplace=True.

Таким образом, в случае с нашим набором данных эта операция удалит 128 строк, где revenue_millions равно null, и 64 строки, где metascore равно null. Очевидно, что это расточительство, поскольку в других столбцах этих удаленных строк есть совершенно хорошие данные. Поэтому далее мы рассмотрим импутацию.

Помимо отбрасывания строк, вы также можете отбросить столбцы с нулевыми значениями, задав axis=1:

movies_df.dropna(axis=1)

В нашем наборе данных эта операция отбросит столбцы revenue_millions и metascore.


Зачем нужен параметр axis=1?

Не сразу понятно, откуда взялся axis и почему для того, чтобы он влиял на столбцы, нужно, чтобы он был равен 1. Чтобы разобраться просто посмотрите на вывод .shape:

movies_df.shape

# Результат:
# (1000, 11)

Как мы уже говорили, это кортеж, который представляет форму DataFrame, т.е. 1000 строк и 11 столбцов. Обратите внимание, что строки находятся под индексом 0 этого кортежа, а столбцы — под индексом 1. Вот почему axis=1 влияет на столбцы. Это берет свое начало в NumPy, и это отличный пример того, почему изучение NumPy стоит вашего времени.


Импутация

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

Бывают случаи, когда отбрасывание всех строк с нулевыми значениями удаляет слишком большой кусок из вашего набора данных. В таких случаях можно заменить null другим значением, обычно средним или медианным значением этого столбца.

Давайте рассмотрим импутацию недостающих значений в столбце revenue_millions. Сначала мы выделим этот столбец в отдельную переменную:

revenue = movies_df['revenue_millions']

Использование квадратных скобок — это общий способ выборки столбцов в DataFrame.

Если вы помните, когда мы создавали DataFrame с нуля, ключи словаря стали названиями столбцов. Теперь, когда мы выбираем столбцы DataFrame, мы используем скобки точно так же, как если бы мы обращались к словарю Python.

revenue теперь содержит Series:

revenue.head()

Результат:

Title
Guardians of the Galaxy    333.13
Prometheus                 126.46
Split                      138.12
Sing                       270.32
Suicide Squad              325.02
Name: revenue_millions, dtype: float64

Немного другое форматирование, чем в DataFrame, но у нас все равно есть индекс Title.

Мы будем вводить недостающие значения выручки (revenue), используя среднее значение. Получаем его:

revenue_mean = revenue.mean()

revenue_mean

# Результат:
# 82.95637614678897

Получив среднее значение, заполним нули с помощью функции fillna():

revenue.fillna(revenue_mean, inplace=True)

Теперь мы заменили все нули в revenue средним значением столбца. Обратите внимание, что, используя inplace=True, мы изменили исходный movies_df:

movies_df.isnull().sum()
rank                 0
genre                0
description          0
director             0
actors               0
year                 0
runtime              0
rating               0
votes                0
revenue_millions     0
metascore           64
dtype: int64

Замена всего столбца одним и тем же значением — это базовый пример. Было бы лучше попробовать импутацию с привязкой к жанру или режиссеру.

Например, можно найти среднее значение дохода, полученного в каждом жанре по отдельности, и импутировать нули в каждом жанре средним значением этого жанра.

Теперь давайте рассмотрим другие способы изучения набора данных.

Понимание ваших переменных

Используя describe() для всего DataFrame, мы можем получить сводку распределения непрерывных переменных:

movies_df.describe()
rank year runtime rating votes revenue_millions metascore
count 1000.000000 1000.000000 1000.000000 1000.000000 1.000000e+03 1000.000000 936.000000
mean 500.500000 2012.783000 113.172000 6.723200 1.698083e+05 82.956376 58.985043
std 288.819436 3.205962 18.810908 0.945429 1.887626e+05 96.412043 17.194757
min 1.000000 2006.000000 66.000000 1.900000 6.100000e+01 0.000000 11.000000
25% 250.750000 2010.000000 100.000000 6.200000 3.630900e+04 17.442500 47.000000
50% 500.500000 2014.000000 111.000000 6.800000 1.107990e+05 60.375000 59.500000
75% 750.250000 2016.000000 123.000000 7.400000 2.399098e+05 99.177500 72.000000
max 1000.000000 2016.000000 191.000000 9.000000 1.791916e+06 936.630000 100.000000

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

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

movies_df['genre'].describe()
count                        1000
unique                        207
top       Action,Adventure,Sci-Fi
freq                           50
Name: genre, dtype: object

Это говорит нам о том, что столбец genre имеет 207 уникальных значений, а топовое значение — Action/Adventure/Sci-Fi, которое встречается 50 раз (freq).

Функция .value_counts() позволяет определить частотность всех значений в столбце:

movies_df['genre'].value_counts().head(10)
Action,Adventure,Sci-Fi       50
Drama                         48
Comedy,Drama,Romance          35
Comedy                        32
Drama,Romance                 31
Action,Adventure,Fantasy      27
Comedy,Drama                  27
Animation,Adventure,Comedy    27
Comedy,Romance                26
Crime,Drama,Thriller          24
Name: genre, dtype: int64

Отношения между непрерывными переменными

Используя метод корреляции .corr(), мы можем сгенерировать отношения между непрерывными переменными:

movies_df.corr()
rank year runtime rating votes revenue_millions metascore
rank 1.000000 -0.261605 -0.221739 -0.219555 -0.283876 -0.252996 -0.191869
year -0.261605 1.000000 -0.164900 -0.211219 -0.411904 -0.117562 -0.079305
runtime -0.221739 -0.164900 1.000000 0.392214 0.407062 0.247834 0.211978
rating -0.219555 -0.211219 0.392214 1.000000 0.511537 0.189527 0.631897
votes -0.283876 -0.411904 0.407062 0.511537 1.000000 0.607941 0.325684
revenue_millions -0.252996 -0.117562 0.247834 0.189527 0.607941 1.000000 0.133328
metascore -0.191869 -0.079305 0.211978 0.631897 0.325684 0.133328 1.000000

Корреляционные таблицы — это числовое представление бивариантных отношений в наборе данных.

Положительные числа указывают на положительную корреляцию: одно увеличивается — и другое увеличивается. А отрицательные числа представляют обратную корреляцию: одно увеличивается — другое уменьшается. 1.0 означает идеальную корреляцию.

Итак, глядя на первую строку, первый столбец, мы видим, что rank имеет идеальную корреляцию с самим собой, что очевидно. С другой стороны, корреляция между votes и revenue_millions составляет 0.6. Уже немного интереснее.

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

Теперь давайте подробнее рассмотрим манипулирование DataFrames.

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

Важно отметить, что, хотя многие методы DataFrame и Series одинаковы, они имеют разные атрибуты. Поэтому вам необходимо знать, с каким типом вы работаете, иначе получите ошибки атрибутов.

Давайте сначала рассмотрим работу со столбцами.

Получение данных по столбцам

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

genre_col = movies_df['genre']

type(genre_col)

# Результат:
# pandas.core.series.Series

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

genre_col = movies_df[['genre']]

type(genre_col)


# Результат:
# pandas.core.frame.DataFrame

Поскольку это просто список, добавить еще одно имя столбца очень просто:

subset = movies_df[['genre', 'rating']]

subset.head()
genre rating
Title
Guardians of the Galaxy Action,Adventure,Sci-Fi 8.1
Prometheus Adventure,Mystery,Sci-Fi 7.0
Split Horror,Thriller 7.3
Sing Animation,Comedy,Family 7.2
Suicide Squad Action,Adventure,Fantasy 6.2

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

Получение данных по строкам

Для строк у нас есть два варианта:

  • .loc — определяет местоположение по имени
  • .iloc — определяет местоположение по числовому индексу.

Помните, что наш индекс — Title, поэтому для использования .loc мы передаем ему название фильма:

prom = movies_df.loc["Prometheus"]

prom
rank                                                                2
genre                                        Adventure,Mystery,Sci-Fi
description         Following clues to the origin of mankind, a te...
director                                                 Ridley Scott
actors              Noomi Rapace, Logan Marshall-Green, Michael Fa...
year                                                             2012
runtime                                                           124
rating                                                              7
votes                                                          485820
revenue_millions                                               126.46
metascore                                                          65
Name: Prometheus, dtype: object

С другой стороны, используя iloc, мы передаем ему числовой индекс «Prometheus»:

prom = movies_df.iloc[1]

loc и iloc можно рассматривать как аналог среза списка в Python. Чтобы это подчеркнуть, давайте сделаем выборку из несколько строк.

Как бы вы сделали это со списком? В Python мы указываем индексы начала и конца среза в квадратных скобках, например example_list[1:4]. В pandas это работает точно так же:

movie_subset = movies_df.loc['Prometheus':'Sing']

movie_subset = movies_df.iloc[1:4]

movie_subset
rank genre description director actors year runtime rating votes revenue_millions metascore
Title
Prometheus 2 Adventure,Mystery,Sci-Fi Following clues to the origin of mankind, a te… Ridley Scott Noomi Rapace, Logan Marshall-Green, Michael Fa… 2012 124 7.0 485820 126.46 65.0
Split 3 Horror,Thriller Three girls are kidnapped by a man with a diag… M. Night Shyamalan James McAvoy, Anya Taylor-Joy, Haley Lu Richar… 2016 117 7.3 157606 138.12 62.0
Sing 4 Animation,Comedy,Family In a city of humanoid animals, a hustling thea… Christophe Lourdelet Matthew McConaughey,Reese Witherspoon, Seth Ma… 2016 108 7.2 60545 270.32 59.0

Одно важное различие между использованием .loc и .iloc для выбора нескольких строк заключается в том, что movies_df.loc['Prometheus':'Sing'] включает фильм Sing в результат, а при использовании movies_df.iloc[1:4] фильм с индексом 4 («Suicide Squad») в результат не включен.

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

Выборка по условиям

Мы рассмотрели, как выбирать столбцы и строки, но что, если мы хотим сделать выборку по какому-нибудь условию?

Допустим, мы хотим отфильтровать наш DataFrame movies, чтобы показать только фильмы режиссера Ридли Скотта или фильмы с рейтингом больше или равным 8.0.

Для этого мы берем столбец из DataFrame и применяем к нему булево условие. Вот пример булева условия:

condition = (movies_df['director'] == "Ridley Scott")

condition.head()
Title
Guardians of the Galaxy    False
Prometheus                  True
Split                      False
Sing                       False
Suicide Squad              False
Name: director, dtype: bool

Подобно isnull(), это возвращает Series из значений True и False. True — для фильмов, режиссером которых является Ридли Скотт, и False для остальных.

Мы хотим отфильтровать все фильмы, режиссером которых не был Ридли Скотт, т.е., нам не нужны «False-фильмы». Чтобы вернуть строки, в которых это условие равно True, мы должны передать эту операцию в DataFrame:

movies_df[movies_df['director'] == "Ridley Scott"]
rank genre description director actors year runtime rating votes revenue_millions metascore rating_category
Title
Prometheus 2 Adventure,Mystery,Sci-Fi Following clues to the origin of mankind, a te… Ridley Scott Noomi Rapace, Logan Marshall-Green, Michael Fa… 2012 124 7.0 485820 126.46 65.0 bad
The Martian 103 Adventure,Drama,Sci-Fi An astronaut becomes stranded on Mars after hi… Ridley Scott Matt Damon, Jessica Chastain, Kristen Wiig, Ka… 2015 144 8.0 556097 228.43 80.0 good
Robin Hood 388 Action,Adventure,Drama In 12th century England, Robin and his band of… Ridley Scott Russell Crowe, Cate Blanchett, Matthew Macfady… 2010 140 6.7 221117 105.22 53.0 bad
American Gangster 471 Biography,Crime,Drama In 1970s America, a detective works to bring d… Ridley Scott Denzel Washington, Russell Crowe, Chiwetel Eji… 2007 157 7.8 337835 130.13 76.0 bad
Exodus: Gods and Kings 517 Action,Adventure,Drama The defiant leader Moses rises up against the … Ridley Scott Christian Bale, Joel Edgerton, Ben Kingsley, S… 2014 150 6.0 137299 65.01 52.0 bad

Вы можете привыкнуть к этим условиям, читая их как:

Select movies_df where movies_df director equals Ridley Scott.

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

movies_df[movies_df['rating'] >= 8.6].head(3)
rank genre description director actors year runtime rating votes revenue_millions metascore
Title
Interstellar 37 Adventure,Drama,Sci-Fi A team of explorers travel through a wormhole … Christopher Nolan Matthew McConaughey, Anne Hathaway, Jessica Ch… 2014 169 8.6 1047747 187.99 74.0
The Dark Knight 55 Action,Crime,Drama When the menace known as the Joker wreaks havo… Christopher Nolan Christian Bale, Heath Ledger, Aaron Eckhart,Mi… 2008 152 9.0 1791916 533.32 82.0
Inception 81 Action,Adventure,Sci-Fi A thief, who steals corporate secrets through … Christopher Nolan Leonardo DiCaprio, Joseph Gordon-Levitt, Ellen… 2010 148 8.8 1583625 292.57 74.0

Мы можем сделать более сложные условия, используя логические операторы | для «или» и & для «и».

Давайте отфильтруем DataFrame, чтобы показать только фильмы Кристофера Нолана ИЛИ Ридли Скотта:

movies_df[(movies_df['director'] == 'Christopher Nolan') | (movies_df['director'] == 'Ridley Scott')].head()
rank genre description director actors year runtime rating votes revenue_millions metascore
Title
Prometheus 2 Adventure,Mystery,Sci-Fi Following clues to the origin of mankind, a te… Ridley Scott Noomi Rapace, Logan Marshall-Green, Michael Fa… 2012 124 7.0 485820 126.46 65.0
Interstellar 37 Adventure,Drama,Sci-Fi A team of explorers travel through a wormhole … Christopher Nolan Matthew McConaughey, Anne Hathaway, Jessica Ch… 2014 169 8.6 1047747 187.99 74.0
The Dark Knight 55 Action,Crime,Drama When the menace known as the Joker wreaks havo… Christopher Nolan Christian Bale, Heath Ledger, Aaron Eckhart,Mi… 2008 152 9.0 1791916 533.32 82.0
The Prestige 65 Drama,Mystery,Sci-Fi Two stage magicians engage in competitive one-… Christopher Nolan Christian Bale, Hugh Jackman, Scarlett Johanss… 2006 130 8.5 913152 53.08 66.0
Inception 81 Action,Adventure,Sci-Fi A thief, who steals corporate secrets through … Christopher Nolan Leonardo DiCaprio, Joseph Gordon-Levitt, Ellen… 2010 148 8.8 1583625 292.57 74.0

Нам нужно обязательно группировать части условия при помощи круглых скобок, чтобы Python знал, как оценивать условие в целом.

Используя метод isin(), мы могли бы сделать это более лаконично:

movies_df[movies_df['director'].isin(['Christopher Nolan', 'Ridley Scott'])].head()

Результат получим тот же.

Допустим, нам нужны все фильмы, которые были выпущены в период с 2005 по 2010 год, имеют рейтинг выше 8.0, но доходность которых меньше 25-го процентиля.

Вот как мы могли бы все это сделать:

movies_df[
    ((movies_df['year'] >= 2005) & (movies_df['year'] <= 2010))
    & (movies_df['rating'] > 8.0)
    & (movies_df['revenue_millions'] < movies_df['revenue_millions'].quantile(0.25))
]
rank genre description director actors year runtime rating votes revenue_millions metascore
Title
3 Idiots 431 Comedy,Drama Two friends are searching for their long lost … Rajkumar Hirani Aamir Khan, Madhavan, Mona Singh, Sharman Joshi 2009 170 8.4 238789 6.52 67.0
The Lives of Others 477 Drama,Thriller In 1984 East Berlin, an agent of the secret po… Florian Henckel von Donnersmarck Ulrich Mühe, Martina Gedeck,Sebastian Koch, Ul… 2006 137 8.5 278103 11.28 89.0
Incendies 714 Drama,Mystery,War Twins journey to the Middle East to discover t… Denis Villeneuve Lubna Azabal, Mélissa Désormeaux-Poulin, Maxim… 2010 131 8.2 92863 6.86 80.0
Taare Zameen Par 992 Drama,Family,Music An eight-year-old boy is thought to be a lazy … Aamir Khan Darsheel Safary, Aamir Khan, Tanay Chheda, Sac… 2007 165 8.5 102697 1.20 42.0

Если вы помните, когда мы использовали .describe(), 25-й процентиль по выручке (revenue) составлял примерно 17.4, и мы можем получить доступ к этому значению напрямую, используя метод quantile() с плавающим значением 0.25.

Итак, здесь у нас есть только четыре фильма, которые соответствуют этому критерию.

Применение функций

DataFrame или Series можно итерировать, как списки, но это очень медленно — особенно для больших наборов данных.

Эффективной альтернативой является применение функции к набору данных. Например, мы можем создать дополнительный столбец, где у каждого фильма будет оценка его рейтинга: «good» или «bad». Оценку «good» получит рейтинг 8.0 или выше.

Сначала мы создадим функцию, которая, получив рейтинг, определит, хороший он или плохой:

def rating_function(x):
    if x >= 8.0:
        return "good"
    else:
        return "bad"

Теперь мы хотим применить эту функцию ко всему столбцу рейтинга. Это делается при помощи метода apply():

movies_df["rating_category"] = movies_df["rating"].apply(rating_function)

movies_df.head(2)
rank genre description director actors year runtime rating votes revenue_millions metascore rating_category
Title
Guardians of the Galaxy 1 Action,Adventure,Sci-Fi A group of intergalactic criminals are forced … James Gunn Chris Pratt, Vin Diesel, Bradley Cooper, Zoe S… 2014 121 8.1 757074 333.13 76.0 good
Prometheus 2 Adventure,Mystery,Sci-Fi Following clues to the origin of mankind, a te… Ridley Scott Noomi Rapace, Logan Marshall-Green, Michael Fa… 2012 124 7.0 485820 126.46 65.0 bad

Метод .apply() пропускает каждое значение в столбце rating через rating_function и затем возвращает новый Series. Затем этот Series присваивается новому столбцу под названием rating_category.

Вы также можете использовать анонимные функции. Эта лямбда-функция дает тот же результат, что и функция rating_function:

movies_df["rating_category"] = movies_df["rating"].apply(lambda x: 'good' if x >= 8.0 else 'bad')

movies_df.head(2)

В целом, использование apply() будет намного быстрее, чем итерация по строкам вручную, поскольку pandas использует векторизацию.

Хорошим примером использования функции apply() является обработка естественного языка (NLP). Вам нужно будет применять всевозможные функции очистки текста к строкам, чтобы подготовить их к машинному обучению.

Кратко о графиках

Еще одна замечательная особенность pandas — интеграция с Matplotlib. Благодаря ей вы получаете возможность строить графики непосредственно на основе DataFrames и Series. Для начала нам нужно импортировать Matplotlib (pip install matplotlib):

import matplotlib.pyplot as plt
plt.rcParams.update({'font.size': 20, 'figure.figsize': (10, 8)}) # set font and plot size

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


Совет по построению графиков

Для категориальных переменных используйте столбчатые диаграммы* и ящики с усами.

Для непрерывных переменных используйте гистограммы, диаграммы рассеяния, линейные графики и ящики с усами.


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

movies_df.plot(kind='scatter', x='rating', y='revenue_millions', title='Revenue (millions) vs Rating');
Диаграмма рассеяния, показывающая зависимость доходности и рейтинга

Что это за точка с запятой? Это не синтаксическая ошибка, а способ скрыть вывод <matplotlib.axes._subplots.AxesSubplot at 0x26613b5cc18> при построении графиков в Jupyter Notebook.

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

movies_df['rating'].plot(kind='hist', title='Rating');
Столбчатая диаграмма для столбца рейтинга

Помните пример использования .describe() в начале этого руководства? Так вот, там есть графическое представление межквартильного интервала, которое называется ящик с усами (или усиковая диаграмма). Давайте вспомним, что дает нам describe() применительно к столбцу рейтингов:

movies_df['rating'].describe()
count    1000.000000
mean        6.723200
std         0.945429
min         1.900000
25%         6.200000
50%         6.800000
75%         7.400000
max         9.000000
Name: rating, dtype: float64

Мы можем визуализировать эти данные с помощью усиковой диаграммы:

movies_df['rating'].plot(kind="box");
Усиковая диаграмма рейтинга фильмов

Легенда диаграммы

Объединив категориальные и непрерывные данные, можно создать усиковую диаграмму доходности с группировкой по оценке рейтинга:

movies_df.boxplot(column='revenue_millions', by='rating_category');
Усиковая диаграмма доходности фильмов с группировкой по рейтингу (плохой-хороший)

Такова общая идея построения графиков с помощью pandas. Существует слишком много графиков, в этой статье все не разберешь. Поэтому обязательно почитайте документацию по plot() для получения дополнительной информации.

Итоги

Исследование, очистка, преобразование и визуализация данных с помощью pandas в Python — это важный навык в науке о данных. Просто очистка и преобразование данных — это 80% работы датасайентиста. После нескольких проектов и некоторой практики вы будете хорошо владеть большей частью основ.

Чтобы продолжать совершенствоваться, почитайте подробные руководства, предлагаемые официальной документацией pandas, пройдите несколько Kaggle kernels и продолжайте работать над собственными проектами!

Перевод статьи George McIntire, Brendan Martin и Lauren Washington «Python Pandas Tutorial: A Complete Introduction for Beginners».

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

Для установки pandas выполним в командной строке команду:

pip install pandas

Во всех примерах предполагается, что библиотеки numpy и pandas импортированы следующим образом:

import numpy as np
import pandas as pd

В библиотеке pandas определены два класса объектов для работы с данными:

  • Series — одномерный массив, который может хранить значения любого типа данных;
  • DataFrame — двумерный массив (таблица), в котором столбцами являются объекты класса Series.

Создать объект класса Series можно следующим образом:

s = pd.Series(data, index=index)

В качестве data могут выступать: массив numpy, словарь, число. В аргумент index передаётся список меток осей. Метка может быть числом, но чаще используются метки-строки.

Если data является массивом numpy, то index должен иметь такую же длину, как и data. Если аргумент index не передаётся, то по умолчанию для index автоматически назначается список [0, …, len(data) — 1]:

s = pd.Series(np.arange(5), index=["a", "b", "c", "d", "e"])
print(s)
print()
s = pd.Series(np.linspace(0, 1, 5))
print(s)

Вывод программы:

a    0
b    1
c    2
d    3
e    4
dtype: int32

0    0.00
1    0.25
2    0.50
3    0.75
4    1.00
dtype: float64

Из вывода программы видно, что Series фактически является аналогом словаря, так как вместо числовых индексов может использовать метки в виде строк.

Если data задаётся словарём, а index не передаётся, то в качестве индекса используются ключи-словари. Если index передаётся, то его длина может не совпадать с длиной словаря data. В таком случае по индексам, для которых нет ключа с соответствующим значением в словаре, будут храниться значения NaN — стандартное обозначение отсутствия данных в библиотеке pandas.

d = {"a": 10, "b": 20, "c": 30, "g": 40}
print(pd.Series(d))
print()
print(pd.Series(d, index=["a", "b", "c", "d"]))
a    10
b    20
c    30
g    40
dtype: int64

a    10.0
b    20.0
c    30.0
d     NaN
dtype: float64

Если data задаётся числом, то index нужно обязательно передать. Количество элементов в Series будет равно числу меток в index, а значения будут равны data:

index = ["a", "b", "c"]
print(pd.Series(5, index=index))

Вывод программы:

a    5
b    5
c    5
dtype: int64

Для Series доступно взятие элемента по индексу, срезы, поэлементные математические операции аналогично массивам numpy.

s = pd.Series(np.arange(5), index=["a", "b", "c", "d", "e"])
print("Выбор одного элемента")
print(s["a"])
print("Выбор нескольких элементов")
print(s[["a", "d"]])
print("Срез")
print(s[1:])
print("Поэлементное сложение")
print(s + s)

Вывод программы:

Выбор одного элемента
0
Выбор нескольких элементов
a    0
d    3
dtype: int32
Срез
b    1
c    2
d    3
e    4
dtype: int32
Поэлементное сложение
a    0
b    2
c    4
d    6
e    8
dtype: int32

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

s = pd.Series(np.arange(5), index=["a", "b", "c", "d", "e"])
print("Фильтрация")
print(s[s > 2])

Вывод программы:

Фильтрация
d    3
e    4
dtype: int32

Объекты Series имеют атрибут name со значением имени набора данных, а также атрибут index.name с именем для индексов:

s = pd.Series(np.arange(5), index=["a", "b", "c", "d", "e"])
s.name = "Данные"
s.index.name = "Индекс"
print(s)

Вывод программы:

Индекс
a    0
b    1
c    2
d    3
e    4
Name: Данные, dtype: int32

Объект класса DataFrame работает с двумерными табличными данными. Создать DataFrame проще всего из словаря Python следующим образом:

students_marks_dict = {"student": ["Студент_1", "Студент_2", "Студент_3"],
                       "math": [5, 3, 4],
                       "physics": [4, 5, 5]}
students = pd.DataFrame(students_marks_dict)
print(students)

Вывод программы:

     student  math  physics
0  Студент_1     5        4
1  Студент_2     3        5
2  Студент_3     4        5

У объекта класса DataFrame есть индексы по строкам (index) и столбцам (columns):

print(students.index)
print(students.columns)

Вывод программы:

RangeIndex(start=0, stop=3, step=1)
Index(['student', 'math', 'physics'], dtype='object')

Для индекса по строке по умолчанию задаётся числовое значение. Значения индекса можно заменить путём записи списка в атрибут index:

students.index = ["A", "B", "C"]
print(students)

Вывод программы:

     student  math  physics
A  Студент_1     5        4
B  Студент_2     3        5
C  Студент_3     4        5

Для доступа к записям таблицы по строковой метке используется атрибут loc. При использовании строковой метки доступна операция среза:

print(students.loc["B":])

Вывод программы:

     student  math  physics
B  Студент_2     3        5
C  Студент_3     4        5

Убедимся, что столбцы у DataFrame являются объектами класса Series:

print(type(students["student"]))

Вывод программы:

<class 'pandas.core.series.Series'>

Обычно табличные данные хранятся в файлах. Такие наборы данных принято называть дата-сетами. Файлы с дата-сетом могут иметь различный формат. Pandas поддерживает операции чтения и записи для CSV, Excel 2007+, SQL, HTML, JSON, буфер обмена и др.

Несколько примеров, как получить дата-сет из файлов разных форматов:

  • CSV. Используется функция read_csv(). Аргумент file является строкой, в которой записан путь до файла с дата-сетом. Для записи данных из DataFrame в CSV-файл используется метод to_csv(file).
  • Excel. Используется функция read_excel(). Для записи данных из DataFrame в Excel-файл используется метод to_excel().
  • JSON. Используется функция read_json(). Для записи данных из DataFrame в JSON используется метод to_json().

Для работы с другими форматами файлов в pandas есть функции, работающие аналогично рассмотренным. С ними можно ознакомиться в документации.

Одним из самых популярных форматов хранения табличных данных является CSV (Comma Separated Values, значения с разделителем-запятой). В файлах этого формата данные хранятся в текстовом виде. Строки таблицы записываются в файле с новой строки, а столбцы разделяются определённым символом, чаще всего запятой ‘,’ или точкой с запятой ‘;’. Первая строка, как правило, содержит заголовки столбцов таблицы. Пример части CSV-файла с информацией о результатах прохождения тестов студентами и некоторой дополнительной информацией:

"gender","race/ethnicity","parental level of education","lunch","test preparation course","math score","reading score","writing score"
"female","group B","bachelor's degree","standard","none","72","72","74"
"female","group C","some college","standard","completed","69","90","88"

Для дальнейшей работы скачайте данный файл с дата-сетом.

Получим дата-сет из CSV-файла с данными о студентах:

import numpy as np
import pandas as pd

students = pd.read_csv("StudentsPerformance.csv")

Полученный объект students относится к классу DataFrame.

Для получения первых n строк дата-сета используется метод head(n). По умолчанию возвращается пять первых строк:

print(students.head())

Вывод программы:

   gender race/ethnicity  ... reading score writing score
0  female        group B  ...            72            74
1  female        group C  ...            90            88
2  female        group B  ...            95            93
3    male        group A  ...            57            44
4    male        group C  ...            78            75

[5 rows x 8 columns]

Для получения последних n строк используется метод tail(n). По умолчанию возвращается пять последних строк:

print(student.tail(3))

Вывод программы:

     gender race/ethnicity  ... reading score writing score
997  female        group C  ...            71            65
998  female        group D  ...            78            77
999  female        group D  ...            86            86

[3 rows x 8 columns]

Для получения части дата-сета можно использовать срез:

print(students[10:13])
    gender race/ethnicity  ... reading score writing score
10    male        group C  ...            54            52
11    male        group D  ...            52            43
12  female        group B  ...            81            73

[3 rows x 8 columns]

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

print(students[students["test preparation course"] == "completed"]["math score"].head())

Вывод программы:

1     69
6     88
8     64
13    78
18    46
Name: math score, dtype: int64

Выведем пять лучших результатов тестов по трём дисциплинам для предыдущей выборки с помощью сортировки методом sort_values(). Сортировка по умолчанию производится в порядке возрастания значений. Для сортировки по убыванию в именованный аргумент ascending передаётся значение False.

with_course = students[students["test preparation course"] == "completed"]
print(with_course[["math score",
                   "reading score",
                   "writing score"]].sort_values(["math score",
                                                  "reading score",
                                                  "writing score"], ascending=False).head())

Вывод программы:

     math score  reading score  writing score
916         100            100            100
149         100            100             93
625         100             97             99
623         100             96             86
114          99            100            100

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

with_course = students[students["test preparation course"] == "completed"]
students["total score"] = students["math score"] + students["reading score"] + students["writing score"]
print(students.sort_values(["total score"], ascending=False).head())

Вывод программы:

     gender race/ethnicity  ... writing score total score
916    male        group E  ...           100         300
458  female        group E  ...           100         300
962  female        group E  ...           100         300
114  female        group E  ...           100         299
179  female        group D  ...           100         297

[5 rows x 9 columns]

Чтобы в таблицу добавить колонку, подойдёт метод assign(). Данный метод даёт возможность создавать колонки при помощи лямбда-функции. Обратите внимание: данный метод возвращает новую таблицу, а не меняет исходную. Перепишем предыдущий пример с использованием assign():

scores = students.assign(total_score=lambda x: x["math score"] + x["reading score"] + x["writing score"])
print(scores.sort_values(["total_score"], ascending=False).head())

Вывод программы:

     gender race/ethnicity  ... writing score total_score
916    male        group E  ...           100         300
458  female        group E  ...           100         300
962  female        group E  ...           100         300
114  female        group E  ...           100         299
179  female        group D  ...           100         297

При анализе данных часто требуется сгруппировать записи по какому-то признаку. Для выполнения операции группировки в pandas используется метод groupby(). Сама по себе группировка для рассматриваемого дата-сета даёт мало информации. Поэтому воспользуемся методом count(), чтобы определить количество сгруппированных записей. Получим информацию о количестве студентов мужского и женского пола (поле «gender»), прошедших курс по подготовке к тестированию (поле «test preparation course»):

print(students.groupby(["gender", "test preparation course"])["writing score"].count())

Вывод программы:

gender  test preparation course
female  completed                  184
        none                       334
male    completed                  174
        none                       308
Name: race/ethnicity, dtype: int64

Если необходимо выполнить операцию, которая позволяет получить сводные данные, то такая операция называется агрегацией. Примерами функции агрегации являются сумма, среднее арифметическое, минимум, максимум и др. Для применения функции агрегации к нескольким столбцам подойдёт метод agg(), в который можно передать словарь с ключами — названиями столбцов, а значения по ключам могут быть списками функций агрегации.

Покажем использование агрегации на примере. Определим среднее арифметическое и медианное значения балла за тест по математике для студентов мужского и женского пола в зависимости от прохождения подготовительного курса:

agg_functions = {"math score": ["mean", "median"]}
print(students.groupby(["gender", "test preparation course"]).agg(agg_functions))

Вывод программы:

                               math score       
                                     mean median
gender test preparation course                  
female completed                67.195652   67.0
       none                     61.670659   62.0
male   completed                72.339080   73.0
       none                     66.688312   67.0

Для визуализации данных pandas использует библиотеку matplotlib. Для её установки выполните в командной строке следующую команду:

pip install matplotlib

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

import matplotlib.pyplot as plt

Построим гистограмму, которая показывает распределение количества студентов по баллам за тест по математике:

plt.hist(students["math score"], label="Тест по математике")
plt.xlabel("Баллы за тест")
plt.ylabel("Количество студентов")
plt.legend()
plt.show()

В результате работы программы получим следующую гистограмму:

Ещё по теме

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

Python pandas

pandas это высокоуровневая Python библиотека для анализа данных. Почему я её называю высокоуровневой, потому что построена она поверх более низкоуровневой библиотеки NumPy (написана на Си), что является большим плюсом в производительности. В экосистеме Python, pandas является наиболее продвинутой и быстроразвивающейся библиотекой для обработки и анализа данных. В своей работе мне приходится пользоваться ею практически каждый день, поэтому я пишу эту краткую заметку для того, чтобы в будущем ссылаться к ней, если вдруг что-то забуду. Также надеюсь, что читателям блога заметка поможет в решении их собственных задач с помощью pandas, и послужит небольшим введением в возможности этой библиотеки.

DataFrame и Series

Чтобы эффективно работать с pandas, необходимо освоить самые главные структуры данных библиотеки: DataFrame и Series. Без понимания что они из себя представляют, невозможно в дальнейшем проводить качественный анализ.

Series

Структура/объект Series представляет из себя объект, похожий на одномерный массив (питоновский список, например), но отличительной его чертой является наличие ассоциированных меток, т.н. индексов, вдоль каждого элемента из списка. Такая особенность превращает его в ассоциативный массив или словарь в Python.

>>> import pandas as pd
>>> my_series = pd.Series([5, 6, 7, 8, 9, 10])
>>> my_series
0     5
1     6
2     7
3     8
4     9
5    10
dtype: int64
>>> 

В строковом представлении объекта Series, индекс находится слева, а сам элемент справа. Если индекс явно не задан, то pandas автоматически создаёт RangeIndex от 0 до N-1, где N общее количество элементов. Также стоит обратить, что у Series есть тип хранимых элементов, в нашем случае это int64, т.к. мы передали целочисленные значения.

У объекта Series есть атрибуты через которые можно получить список элементов и индексы, это values и index соответственно.

>>> my_series.index
RangeIndex(start=0, stop=6, step=1)
>>> my_series.values
array([ 5,  6,  7,  8,  9, 10], dtype=int64) 

Доступ к элементам объекта Series возможны по их индексу (вспоминается аналогия со словарем и доступом по ключу).

>>> my_series[4]
9

Индексы можно задавать явно:

>>> my_series2 = pd.Series([5, 6, 7, 8, 9, 10], index=['a', 'b', 'c', 'd', 'e', 'f'])
>>> my_series2['f']
10

Делать выборку по нескольким индексам и осуществлять групповое присваивание:

>>> my_series2[['a', 'b', 'f']]
a     5
b     6
f    10
dtype: int64
>>> my_series2[['a', 'b', 'f']] = 0
>>> my_series2
a    0
b    0
c    7
d    8
e    9
f    0
dtype: int64

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

>>> my_series2[my_series2 > 0]
c    7
d    8
e    9
dtype: int64

>>> my_series2[my_series2 > 0] * 2
c    14
d    16
e    18
dtype: int64

Если Series напоминает нам словарь, где ключом является индекс, а значением сам элемент, то можно сделать так:

>>> my_series3 = pd.Series({'a': 5, 'b': 6, 'c': 7, 'd': 8})
>>> my_series3
a    5
b    6
c    7
d    8
dtype: int64
>>> 'd' in my_series3
True

У объекта Series и его индекса есть атрибут name, задающий имя объекту и индексу соответственно.

>>> my_series3.name = 'numbers'
>>> my_series3.index.name = 'letters'
>>> my_series3
letters
a    5
b    6
c    7
d    8
Name: numbers, dtype: int64

Индекс можно поменять «на лету», присвоив список атрибуту index объекта Series

>>> my_series3.index = ['A', 'B', 'C', 'D']
>>> my_series3
A    5
B    6
C    7
D    8
Name: numbers, dtype: int64

Имейте в виду, что список с индексами по длине должен совпадать с количеством элементов в Series.

DataFrame

Объект DataFrame лучше всего представлять себе в виде обычной таблицы и это правильно, ведь DataFrame является табличной структурой данных. В любой таблице всегда присутствуют строки и столбцы. Столбцами в объекте DataFrame выступают объекты Series, строки которых являются их непосредственными элементами.

DataFrame проще всего сконструировать на примере питоновского словаря:

>>> df = pd.DataFrame({
...     'country': ['Kazakhstan', 'Russia', 'Belarus', 'Ukraine'],
...     'population': [17.04, 143.5, 9.5, 45.5],
...     'square': [2724902, 17125191, 207600, 603628]
... })
>>> df
   country  population    square
0  Kazakhstan       17.04   2724902
1      Russia      143.50  17125191
2     Belarus        9.50    207600
3     Ukraine       45.50    603628

Чтобы убедиться, что столбец в DataFrame это Series, извлекаем любой:


>>> df['country']
0    Kazakhstan
1        Russia
2       Belarus
3       Ukraine
Name: country, dtype: object
>>> type(df['country'])
<class 'pandas.core.series.Series'>

Объект DataFrame имеет 2 индекса: по строкам и по столбцам. Если индекс по строкам явно не задан (например, колонка по которой нужно их строить), то pandas задаёт целочисленный индекс RangeIndex от 0 до N-1, где N это количество строк в таблице.

>>> df.columns
Index([u'country', u'population', u'square'], dtype='object')
>>> df.index
RangeIndex(start=0, stop=4, step=1)

В таблице у нас 4 элемента от 0 до 3. 

Доступ по индексу в DataFrame

Индекс по строкам можно задать разными способами, например, при формировании самого объекта DataFrame или «на лету»:

>>> df = pd.DataFrame({
...     'country': ['Kazakhstan', 'Russia', 'Belarus', 'Ukraine'],
...     'population': [17.04, 143.5, 9.5, 45.5],
...     'square': [2724902, 17125191, 207600, 603628]
... }, index=['KZ', 'RU', 'BY', 'UA'])
>>> df
       country  population    square
KZ  Kazakhstan       17.04   2724902
RU      Russia      143.50  17125191
BY     Belarus        9.50    207600
UA     Ukraine       45.50    603628
>>> df.index = ['KZ', 'RU', 'BY', 'UA']
>>> df.index.name = 'Country Code'
>>> df
                 country  population    square
Country Code                                  
KZ            Kazakhstan       17.04   2724902
RU                Russia      143.50  17125191
BY               Belarus        9.50    207600
UA               Ukraine       45.50    603628

Как видно, индексу было задано имя — Country Code. Отмечу, что объекты Series из DataFrame будут иметь те же индексы, что и объект DataFrame:

>>> df['country']
Country Code
KZ    Kazakhstan
RU        Russia
BY       Belarus
UA       Ukraine
Name: country, dtype: object

Доступ к строкам по индексу возможен несколькими способами:

  • .loc — используется для доступа по строковой метке
  • .iloc — используется для доступа по числовому значению (начиная от 0)
>>> df.loc['KZ']
country       Kazakhstan
population         17.04
square           2724902
Name: KZ, dtype: object

>>> df.iloc[0]
country       Kazakhstan
population         17.04
square           2724902
Name: KZ, dtype: object

Можно делать выборку по индексу и интересующим колонкам:

>>> df.loc[['KZ', 'RU'], 'population']
Country Code
KZ     17.04
RU    143.50
Name: population, dtype: float64

Как можно заметить, .loc в квадратных скобках принимает 2 аргумента: интересующий индекс, в том числе поддерживается слайсинг и колонки.

>>> df.loc['KZ':'BY', :]
                 country  population    square
Country Code                                  
KZ            Kazakhstan       17.04   2724902
RU                Russia      143.50  17125191
BY               Belarus        9.50    207600

Фильтровать DataFrame с помощью т.н. булевых массивов:

>>> df[df.population > 10][['country', 'square']]
                 country    square
Country Code                      
KZ            Kazakhstan   2724902
RU                Russia  17125191
UA               Ukraine    603628

Кстати, к столбцам можно обращаться, используя атрибут или нотацию словарей Python, т.е. df.population и df[‘population’] это одно и то же.

Сбросить индексы можно вот так:

>>> df.reset_index()
  Country Code     country  population    square
0           KZ  Kazakhstan       17.04   2724902
1           RU      Russia      143.50  17125191
2           BY     Belarus        9.50    207600
3           UA     Ukraine       45.50    603628

pandas при операциях над DataFrame, возвращает новый объект DataFrame.

Добавим новый столбец, в котором население (в миллионах) поделим на площадь страны, получив тем самым плотность:

>>> df['density'] = df['population'] / df['square'] * 1000000
>>> df
                 country  population    square    density
Country Code                                             
KZ            Kazakhstan       17.04   2724902   6.253436
RU                Russia      143.50  17125191   8.379469
BY               Belarus        9.50    207600  45.761079
UA               Ukraine       45.50    603628  75.377550

Не нравится новый столбец? Не проблема, удалим его:

>>> df.drop(['density'], axis='columns')
                 country  population    square
Country Code                                  
KZ            Kazakhstan       17.04   2724902
RU                Russia      143.50  17125191
BY               Belarus        9.50    207600
UA               Ukraine       45.50    603628

Особо ленивые могут просто написать del df[‘density’].

Переименовывать столбцы нужно через метод rename:


>>> df = df.rename(columns={'Country Code': 'country_code'})
>>> df
  country_code     country  population    square
0           KZ  Kazakhstan       17.04   2724902
1           RU      Russia      143.50  17125191
2           BY     Belarus        9.50    207600
3           UA     Ukraine       45.50    603628

В этом примере перед тем как переименовать столбец Country Code, убедитесь, что с него сброшен индекс, иначе не будет никакого эффекта.

Чтение и запись данных

pandas поддерживает все самые популярные форматы хранения данных: csv, excel, sql, буфер обмена, html и многое другое:

Чаще всего приходится работать с csv-файлами. Например, чтобы сохранить наш DataFrame со странами, достаточно написать:

>>> df.to_csv('filename.csv')

Функции to_csv ещё передаются различные аргументы (например, символ разделителя между колонками) о которых подробнее можно узнать в официальной документации.

Считать данные из csv-файла и превратить в DataFrame можно функцией read_csv.

>>> df = pd.read_csv('filename.csv', sep=',')

Аргумент sep указывает разделитесь столбцов. Существует ещё масса способов сформировать DataFrame из различных источников, но наиболее часто используют CSV, Excel и SQL. Например, с помощью функции read_sql, pandas может выполнить SQL запрос и на основе ответа от базы данных сформировать необходимый DataFrame. За более подробной информацией стоит обратиться к официальной документации.

Группировка и агрегирование в pandas

Группировка данных один из самых часто используемых методов при анализе данных. В pandas за группировку отвечает метод .groupby. Я долго думал какой пример будет наиболее наглядным, чтобы продемонстрировать группировку, решил взять стандартный набор данных (dataset), использующийся во всех курсах про анализ данных — данные о пассажирах Титаника. Скачать CSV файл можно тут.

>>> titanic_df = pd.read_csv('titanic.csv')
>>> print(titanic_df.head())
   PassengerID                                           Name PClass    Age  
0            1                   Allen, Miss Elisabeth Walton    1st  29.00   
1            2                    Allison, Miss Helen Loraine    1st   2.00   
2            3            Allison, Mr Hudson Joshua Creighton    1st  30.00   
3            4  Allison, Mrs Hudson JC (Bessie Waldo Daniels)    1st  25.00   
4            5                  Allison, Master Hudson Trevor    1st   0.92   
      Sex  Survived  SexCode  
0  female         1        1  
1  female         0        1  
2    male         0        0  
3  female         0        1  
4    male         1        0  

Необходимо подсчитать, сколько женщин и мужчин выжило, а сколько нет. В этом нам поможет метод .groupby.

>>> print(titanic_df.groupby(['Sex', 'Survived'])['PassengerID'].count())
Sex     Survived
female  0           154
        1           308
male    0           709
        1           142
Name: PassengerID, dtype: int64

А теперь проанализируем в разрезе класса кабины:

>>> print(titanic_df.groupby(['PClass', 'Survived'])['PassengerID'].count())
PClass  Survived
*       0             1
1st     0           129
        1           193
2nd     0           160
        1           119
3rd     0           573
        1           138
Name: PassengerID, dtype: int64

Сводные таблицы в pandas

Термин «сводная таблица» хорошо известен тем, кто не по наслышке знаком с инструментом Microsoft Excel или любым иным, предназначенным для обработки и анализа данных. В pandas сводные таблицы строятся через метод .pivot_table. За основу возьмём всё тот же пример с Титаником. Например, перед нами стоит задача посчитать сколько всего женщин и мужчин было в конкретном классе корабля:

>>> titanic_df = pd.read_csv('titanic.csv')
>>> pvt = titanic_df.pivot_table(index=['Sex'], columns=['PClass'], values='Name', aggfunc='count')

В качестве индекса теперь у нас будет пол человека, колонками станут значения из PClass, функцией агрегирования будет count (подсчёт количества записей) по колонке Name.

>>> print(pvt.loc['female', ['1st', '2nd', '3rd']])
PClass
1st    143.0
2nd    107.0
3rd    212.0
Name: female, dtype: float64

Всё очень просто.

Анализ временных рядов

В pandas очень удобно анализировать временные ряды. В качестве показательного примера я буду использовать цену на акции корпорации Apple за 5 лет по дням. Файл с данными можно скачать тут.

>>> import pandas as pd
>>> df = pd.read_csv('apple.csv', index_col='Date', parse_dates=True)
>>> df = df.sort_index()
>>> print(df.info())
<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 1258 entries, 2017-02-22 to 2012-02-23
Data columns (total 6 columns):
Open         1258 non-null float64
High         1258 non-null float64
Low          1258 non-null float64
Close        1258 non-null float64
Volume       1258 non-null int64
Adj Close    1258 non-null float64
dtypes: float64(5), int64(1)
memory usage: 68.8 KB

Здесь мы формируем DataFrame с DatetimeIndex по колонке Date и сортируем новый индекс в правильном порядке для работы с выборками. Если колонка имеет формат даты и времени отличный от ISO8601, то для правильного перевода строки в нужный тип, можно использовать метод pandas.to_datetime.

Давайте теперь узнаем среднюю цену акции (mean) на закрытии (Close):

>>> df.loc['2012-Feb', 'Close'].mean()
528.4820021999999

А если взять промежуток с февраля 2012 по февраль 2015 и посчитать среднее:

>>> df.loc['2012-Feb':'2015-Feb', 'Close'].mean()
430.43968317018414

А что если нам нужно узнать среднюю цену закрытия по неделям?!

>>> df.resample('W')['Close'].mean()
Date
2012-02-26    519.399979
2012-03-04    538.652008
2012-03-11    536.254004
2012-03-18    576.161993
2012-03-25    600.990001
2012-04-01    609.698003
2012-04-08    626.484993
2012-04-15    623.773999
2012-04-22    591.718002
2012-04-29    590.536005
2012-05-06    579.831995
2012-05-13    568.814001
2012-05-20    543.593996
2012-05-27    563.283995
2012-06-03    572.539994
2012-06-10    570.124002
2012-06-17    573.029991
2012-06-24    583.739993
2012-07-01    574.070004
2012-07-08    601.937489
2012-07-15    606.080008
2012-07-22    607.746011
2012-07-29    587.951999
2012-08-05    607.217999
2012-08-12    621.150003
2012-08-19    635.394003
2012-08-26    663.185999
2012-09-02    670.611995
2012-09-09    675.477503
2012-09-16    673.476007
                 ...    
2016-08-07    105.934003
2016-08-14    108.258000
2016-08-21    109.304001
2016-08-28    107.980000
2016-09-04    106.676001
2016-09-11    106.177498
2016-09-18    111.129999
2016-09-25    113.606001
2016-10-02    113.029999
2016-10-09    113.303999
2016-10-16    116.860000
2016-10-23    117.160001
2016-10-30    115.938000
2016-11-06    111.057999
2016-11-13    109.714000
2016-11-20    108.563999
2016-11-27    111.637503
2016-12-04    110.587999
2016-12-11    111.231999
2016-12-18    115.094002
2016-12-25    116.691998
2017-01-01    116.642502
2017-01-08    116.672501
2017-01-15    119.228000
2017-01-22    119.942499
2017-01-29    121.164000
2017-02-05    125.867999
2017-02-12    131.679996
2017-02-19    134.978000
2017-02-26    136.904999
Freq: W-SUN, Name: Close, dtype: float64

Resampling мощный инструмент при работе с временными рядами (time series), помогающий переформировать выборку так, как удобно вам. Метод resample первым аргументом принимает строку rule. Все доступные значения можно найти в документации.

Визуализация данных в pandas

Для визуального анализа данных, pandas использует библиотеку matplotlib. Продемонстрирую простейший способ визуализации в pandas на примере с акциями Apple.

Берём цену закрытия в промежутке между 2012 и 2017.

>>> import matplotlib.pyplot as plt
>>> new_sample_df = df.loc['2012-Feb':'2017-Feb', ['Close']]
>>> new_sample_df.plot()
>>> plt.show()

И видим вот такую картину:

По оси X, если не задано явно, всегда будет индекс. По оси Y в нашем случае цена закрытия. Если внимательно посмотреть, то в 2014 году цена на акцию резко упала, это событие было связано с тем, что Apple проводила сплит 7 к 1. Так мало кода и уже более-менее наглядный анализ ;)

Эта заметка демонстрирует лишь малую часть возможностей pandas. Со своей стороны я постараюсь по мере своих сил обновлять и дополнять её.

Полезные ссылки

  • pandas cheatsheet
  • Официальная документация pandas
  • Почему Python
  • Python Data Science Handbook

💌 Присоединяйтесь к рассылке

Понравился контент? Пожалуйста, подпишись на рассылку.

#статьи

  • 23 ноя 2022

  • 0

Разбираемся в том, как работает библиотека Pandas и проводим первый анализ данных.

Иллюстрация: Катя Павловская для Skillbox Media

Антон Яценко

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

Python используют для анализа данных и машинного обучения, подключая к нему различные библиотеки: Pandas, Matplotlib, NumPy, TensorFlow и другие. Каждая из них используется для решения конкретных задач.

Сегодня мы поговорим про Pandas: узнаем, для чего нужна эта библиотека, как импортировать её в Python, а также проанализируем свой первый датасет и выясним, в какой стране самый быстрый и самый медленный интернет.

Pandas — главная библиотека в Python для работы с данными. Её активно используют аналитики данных и дата-сайентисты. Библиотека была создана в 2008 году компанией AQR Capital, а в 2009 году она стала проектом с открытым исходным кодом с поддержкой большого комьюнити.

Вот для каких задач используют библиотеку:

Аналитика данных: продуктовая, маркетинговая и другая. Работа с любыми данными требует анализа и подготовки: необходимо удалить или заполнить пропуски, отфильтровать, отсортировать или каким-то образом изменить данные. Pandas в Python позволяет быстро выполнить все эти действия, а в большинстве случаев ещё и автоматизировать их.

Data science и работа с большими данными. Pandas помогает подготовить и провести первичный анализ данных, чтобы потом использовать их в машинном или глубоком обучении.

Статистика. Библиотека поддерживает основные статистические методы, которые необходимы для работы с данными. Например, расчёт средних значений, их распределения по квантилям и другие.

Для анализа данных и машинного обучения обычно используются особые инструменты: Google Colab или Jupyter Notebook. Это специализированные IDE, позволяющие работать с данными пошагово и итеративно, без необходимости создавать полноценное приложение.

В этой статье мы посмотрим на Google Colab, облачное решение для работы с данными, которое можно запустить в браузере на любом устройстве: десктопе, ноутбуке, планшете или даже смартфоне.

Скриншот: Pandas / Skillbox Media

Каждая строчка кода на скриншоте — это одно действие, результат которого Google Colab и Jupyter Notebook сразу демонстрируют пользователю. Это удобно в задачах, связанных с аналитикой и data science.

Устанавливать Pandas при работе с Jupyter Notebook или Colab не требуется. Это стандартная библиотека, которая уже будет доступна сразу после их запуска. Останется только импортировать её в ваш код.

import pandas as pd 

pd — общепринятое сокращение для Pandas в коде. Оно встречается в книгах, статьях и учебных курсах. Используйте его и в своих программах, чтобы не писать длинное pandas.

Данные в Pandas представлены в двух видах: Series и DataFrame. Разберёмся с каждым из них.

Series — это объект, который похож на одномерный массив и может содержать любые типы данных. Проще всего представить его как столбец таблицы, с последовательностью каких-либо значений, у каждого из которых есть индекс — номер строки.

Создадим простой Series:

import pandas as pd #Импортируем библиотеку Pandas.
 
series_example = pd.Series([4, 7, -5, 3]) #Создаём объект Series, содержащий числа.
 
series_example #Выводим объект на экран.

Теперь выведем его на экран:

Скриншот: Pandas / Skillbox Media

Series отображается в виде таблицы с индексами элементов в первом столбце и значениями во втором.

DataFrame — основной тип данных в Pandas, вокруг которого строится вся работа. Его можно представить в виде обычной таблицы с любым количеством столбцов и строк. Внутри ячеек такой «таблицы» могут быть данные самого разного типа: числовые, булевы, строковые и так далее.

У DataFrame есть и индексы строк, и индексы столбцов. Это позволяет удобно сортировать и фильтровать данные, а также быстро находить нужные ячейки.

Создадим простой DataFrame с помощью словаря и посмотрим на его отображение:

import pandas as pd #Импортируем библиотеку Pandas.
 
city = {'Город': ['Москва', 'Санкт-Петербург', 'Новосибирск', 'Екатеринбург'],
        'Год основания': [1147, 1703, 1893, 1723], 
        'Население': [11.9, 4.9, 1.5, 1.4]} #Создаём словарь с нужной информацией о городах.
 
df = pd.DataFrame(city) #Превращаем словарь в DataFrame, используя стандартный метод библиотеки.
 
df #Выводим DataFrame на экран.

Посмотрим на результат:

Скриншот: Pandas / Skillbox Media

Мы видим таблицу, строки которой имеют индексы от 0 до 3, а «индексы» столбцов соответствуют их названиям. Легко заметить, что DataFrame состоит из трёх Series: «Город», «Год основания» и «Население». Оба типа индексов можно использовать для навигации по данным.

Pandas позволяет импортировать данные разными способами. Например, прочесть их из словаря, списка или кортежа. Самый популярный способ — это работа с файлами .csv, которые часто применяются в анализе данных. Для импорта используют команду pd.read_csv().

read_csv имеет несколько параметров для управления импортом:

  • sep — позволяет явно указать разделитель, который используется в импортируемом файле. По умолчанию значение равно ,, что соответствует разделителю данных в файлах формата .csv. Этот параметр полезен при нестандартных разделителях в исходном файле, например табуляции или точки с запятой;
  • dtype — позволяет явно указать на тип данных в столбцах. Полезно в тех случаях, когда формат данных автоматически определился неверно. Например, даты часто импортируются в виде строковых переменных, хотя для них существует отдельный тип.

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

Давайте импортируем датасет с информацией о скорости мобильного и стационарного интернета в отдельных странах. Готовый датасет скачиваем с Kaggle. Параметры для read_csv не указываем, так как наши данные уже подготовлены для анализа.

df = pd.read_csv('/content/Internet Speed 2022.csv')

Теперь посмотрим на получившийся датафрейм:

df

Важно! При работе в Google Colab или Jupyter Notebook для вывода DataFrame или Series на экран не используется команда print. Pandas умеет показывать данные и без неё. Если же написать print(df), то табличная вёрстка потеряется. Попробуйте вывести данные двумя способами и посмотрите на результат.

На экране появилась вот такая таблица:

Скриншот: Pandas / Skillbox Media

В верхней части таблицы мы видим названия столбцов: country (страна), broadband (средняя скорость интернета) и mobile (средняя скорость мобильного интернета). Слева указаны индексы — от 0 до 176. То есть всего у нас 177 строк. В нижней части таблицы Pandas отображает и эту информацию.

Выводить таблицу полностью необязательно. Для первого знакомства с данными достаточно показать пять первых или пять последних строк. Сделать это можно с помощью df.head() или df.tail() соответственно. В скобках можно указать число строк, которое требуется указать. По умолчанию параметр равен 5.

df.head()

Результат:

Скриншот: Pandas / Skillbox Media

Так намного удобнее. Мы можем сразу увидеть названия столбцов и тип данных в столбцах. Также в некоторых ячейках мы видим значение NaN — к нему мы вернёмся позже.

Теперь нам надо изучить импортированные данные. Действовать будем пошагово.

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

df.dtypes

На экране появится таблица с обозначением типа данных в каждом столбце:

Скриншот: Pandas / Skillbox Media

Что мы видим:

  • столбец country представляет собой тип object. Это тип данных для строковых и смешанных значений;
  • столбцы broadband и mobile имеют тип данных float, то есть относятся к числам с плавающей точкой.

Шаг 2. Быстро оцениваем данные и делаем предварительные выводы. Сделать это можно очень просто: для этого в Pandas существует специальный метод describe(). Он показывает среднее со стандартным отклонением, максимальные, минимальные значения переменных и их разделение по квантилям.

Посмотрим на этот метод в деле:

df.describe()

Результат:

Скриншот: Pandas / Skillbox Media

Пройдёмся по каждой строчке:

  • count — это количество заполненных строк в каждом столбце. Мы видим, что в столбце с данными о скорости мобильного интернета есть пропуски.
  • mean — среднее значение скорости обычного и мобильного интернета. Уже можно сделать вывод, что мобильный интернет в большинстве стран медленнее, чем кабельный.
  • std — стандартное отклонение. Важный статистический показатель, показывающий разброс значений.
  • min и max — минимальное и максимальное значение.
  • 25%, 50% и 75% — значения скорости интернета по процентилям. Если не углубляться в статистику, то процентиль — это число, которое показывает распределение значений в выборке. Например, в выборке с мобильным интернетом процентиль 25% показывает, что 25% от всех значений скорости интернета меньше, чем 24,4.

Обратите внимание, что этот метод работает только для чисел. Информация для столбца с названиями стран отсутствует.

Какой вывод делаем? Проводной интернет в большинстве стран работает быстрее, чем мобильный. При этом скорость проводного интернета в 75% случаев не превышает 110 Мбит/сек, а мобильного — 69 Мбит/сек.

Шаг 3. Сортируем и фильтруем записи. В нашем датафрейме данные уже отсортированы от большего к меньшему по скорости проводного интернета. Попробуем найти страну с наилучшим мобильным интернетом. Для этого используем стандартный метод sort_values, который принимает два параметра:

  • название столбца, по которому происходит сортировка, — обязательно должно быть заключено в одинарные или двойные кавычки;
  • параметр ascending= — указывает на тип сортировки. Если мы хотим отсортировать значения от большего к меньшему, то параметру присваиваем False. Для сортировки от меньшего к большему используем True.

Перейдём к коду:

df.sort_values('mobile', ascending=False).head()

Результат:

Скриншот: Pandas / Skillbox Media

Теперь рейтинг стран другой — пятёрка лидеров поменялась (потому что мы отсортировали данные по другому значению). Мы выяснили, что самый быстрый мобильный интернет в ОАЭ.

Но есть нюанс. Если вернуться к первоначальной таблице, отсортированной по скорости проводного интернета, можно заметить, что у лидера — Монако — во втором столбце написано NaN. NaN в Python указывает на отсутствие данных. Поэтому мы не знаем скорость мобильного интернета в Монако из этого датасета и не можем сделать однозначный вывод о лидерах в мире мобильной связи.

Попробуем отфильтровать значения, убрав страны с неизвестной скоростью мобильного интернета, и посмотрим на худшие по показателю страны (если оставить NaN, он будет засорять «дно» таблицы и увидеть реальные значения по самому медленному мобильному интернету будет сложновато).

В Pandas существуют различные способы фильтрации для удаления NaN. Мы воспользуемся методом dropna(), который удаляет все строки с пропусками. Важно, что удаляется полностью строка, содержащая NaN, а не только ячейки с пропущенными значениями в столбце с пропусками.

df.dropna()

Результат:

Скриншот: Pandas / Skillbox Media

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

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

df_without_nan = df.dropna()

Теперь отсортируем полученные результаты по столбцу mobile от меньшего к большему и посмотрим на страну с самым медленным мобильным интернетом:

df_without_nan.sort_values('mobile', ascending=True)

Результат:

Скриншот: Pandas / Skillbox Media

Худший мобильный интернет в Афганистане с небольшим отставанием от Палестины и Венесуэлы.

Pandas в Python — мощная библиотека для анализа данных. В этой статье мы прошли по базовым операциям. Подробнее про работу библиотеки можно узнать в документации. Углубиться в работу с библиотекой можно благодаря специализированным книгам:

  • «Изучаем pandas. Высокопроизводительная обработка и анализ данных в Python» Майкла Хейдта и Артёма Груздева;
  • «Thinking in Pandas: How to Use the Python Data Analysis Library the Right Way», Hannah Stepanek;
  • «Hands-On Data Analysis with Pandas: Efficiently perform data collection, wrangling, analysis, and visualization using Python», Stefanie Molin.

Научитесь: Профессия Python-разработчик
Узнать больше

geeksforgeeks-footer-logo


A-143, 9th Floor, Sovereign Corporate Tower, Sector-136, Noida, Uttar Pradesh — 201305


feedback@geeksforgeeks.org

Понравилась статья? Поделить с друзьями:
  • Клацид суспензия детям инструкция по применению 125 мг детям
  • Casio protrek prw 1500 инструкция на русском
  • Электронные часы telefunken инструкция по применению на русском языке
  • Бордосская жидкость вск инструкция по применению 100 мл
  • Руководство по эксплуатации бмв ф30