Pandas руководство на русском

Начало работы¶

Установка¶

pandas является частью дистрибутива Anaconda и может быть установлен вместе с Anaconda или Miniconda:

pandas можно установить через pip из PyPI.

Устанавливаете конкретную версию? Устанавливаете из исходников? Загляните на страницу расширенной установки.

Подробнее

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

../_images/01_table_dataframe.svg

pandas поддерживает интеграцию со многими форматами файлов или источниками данных «из коробки» (csv, excel, sql, json, parquet и другими). Импорт данных из таких источников обеспечивается функцией с префиксом read_*. Точно так же методы to_* используются для сохранения данных.

../_images/02_io_readwrite.svg

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

../_images/03_subset_columns_rows.svg

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

../_images/04_plot_overview.svg

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

../_images/05_newcolumn_2.svg

Основные статистические показатели (среднее, медиана, минимум, максимум и прочее) легко поддаются вычислению. Стандартный либо пользовательский набор показателей можно получить для всего набора данных, скользящего окна данных или группировок по категориям. Последнее еще называют подходом «разделить-применить-объединить».

../_images/06_groupby.svg

Изменяйте структуру таблицы данных несколькими способами. Функцией melt() можно преобразовать таблицу данных из широкого в длинный (аккуратный) формат, а функцией pivot() — из длинного в широкий формат. Благодаря встроенным агрегирующим функциям сводная таблица создается с помощью одной команды.

../_images/07_melt.svg

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

../_images/08_concat_row.svg

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

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

Предшественники¶

Знакомы ли вы с другим программным обеспечением для обработки табличных данных? Изучите операции, которые эквивалентны в pandas и в программном обеспечении, которое вы уже знаете:

img-top

Язык программирования R предоставляет структуру данных data.frame и несколько пакетов, таких как tidyverse, для удобной обработки данных, аналогично pandas.

img-top

Уже знакомы с SELECT, GROUP BY, JOIN и так далее? Большинство этих запросов к SQL имеют эквиваленты в pandas.

img-top

data set, включенный в пакет статистического программного обеспечения STATA , соответствует DataFrame в pandas. Многие операции из STATA имеют эквивалент в pandas.

img-top

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

img-top

Набор статистического программного обеспечения SAS также предоставляет data set, соответствующий DataFrame в pandas. Кроме того, векторизованные операции SAS, фильтрация, операции обработки строк и многое другое имеют аналогичные функции в pandas.

Учебные материалы¶

Краткий обзор функциональности pandas см. в статье 10 Minutes to pandas.

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

Сообщество выпускает множество учебных пособий, доступных в Интернете. Некоторые материалы включены в список предоставленных сообществом (см. раздел Учебные материалы сообщества).

Пакет 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 предоставляет широкие возможности по обработке и анализу данных, поэтому в этой главе мы рассмотрели лишь базовые методы обработки, анализа и визуализации данных. Для более детального изучения советуем почитать документацию.

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

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

В материале, перевод которого мы публикуем сегодня, мы поговорим об особенностях работы с памятью при использовании pandas, и о том, как, просто подбирая подходящие типы данных, хранящихся в столбцах табличных структур данных DataFrame, снизить потребление памяти почти на 90%.

Работа с данными о бейсбольных матчах

Мы будем работать с данными по бейсбольным играм Главной лиги, собранными за 130 лет и взятыми с Retrosheet.

Изначально эти данные были представлены в виде 127 CSV-файлов, но мы объединили их в один набор данных с помощью csvkit и добавили, в качестве первой строки получившейся таблицы, строку с названиями столбцов. Если хотите, можете загрузить нашу версию этих данных и экспериментировать с ними, читая статью.

Начнём с импорта набора данных и взглянем на его первые пять строк. Их вы можете найти в этой таблице, на листе Фрагмент исходного набора данных.

import pandas as pd

gl = pd.read_csv('game_logs.csv')
gl.head()

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

  • date — Дата проведения игры.
  • v_name — Название команды гостей.
  • v_league — Лига команды гостей.
  • h_name — Название команды хозяев.
  • h_league — Лига команды хозяев.
  • v_score — Очки команды гостей.
  • h_score — Очки команды хозяев.
  • v_line_score — Сводка по очкам команды гостей, например — 010000(10)00.
  • h_line_score — Сводка по очкам команды хозяев, например — 010000(10)0X.
  • park_id — Идентификатор поля, на котором проводилась игра.
  • attendance — Количество зрителей.

Для того чтобы узнать общие сведения об объекте DataFrame, можно воспользоваться методом DataFrame.info(). Благодаря этому методу можно узнать о размере объекта, о типах данных и об использовании памяти.

По умолчанию pandas, ради экономии времени, указывает приблизительные сведения об использовании памяти объектом DataFrame. Нас интересуют точные сведения, поэтому мы установим параметр memory_usage в значение 'deep'.

gl.info(memory_usage='deep')

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

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 171907 entries, 0 to 171906
Columns: 161 entries, date to acquisition_info
dtypes: float64(77), int64(6), object(78)
memory usage: 861.6 MB

Как оказалось, у нас имеется 171,907 строк и 161 столбец. Библиотека pandas автоматически выяснила типы данных. Здесь присутствует 83 столбца с числовыми данными и 78 столбцов с объектами. Объектные столбцы используются для хранения строковых данных, и в тех случаях, когда столбец содержит данные разных типов.

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

Внутреннее представление объекта DataFrame

Внутри pandas столбцы данных группируются в блоки со значениями одинакового типа. Вот пример того, как в pandas хранятся первые 12 столбцов объекта DataFrame.

Внутреннее представление данных разных типов в pandas

Можно заметить, что блоки не хранят сведения об именах столбцов. Происходит это из-за того, что блоки оптимизированы для хранения значений, имеющихся в ячейках таблицы объекта DataFrame. За хранение сведений о соответствии между индексами строк и столбцов набора данных и того, что хранится в блоках однотипных данных, отвечает класс BlockManager. Он играет роль API, который предоставляет доступ к базовым данным. Когда мы читаем, редактируем или удаляем значения, класс DataFrame взаимодействует с классом BlockManager для преобразования наших запросов в вызовы функций и методов.

Каждый тип данных имеет специализированный класс в модуле pandas.core.internals. Например, pandas использует класс ObjectBlock для представления блоков, содержащих строковые столбцы, и класс FloatBlock для представления блоков, содержащих столбцы, хранящие числа с плавающей точкой. Для блоков, представляющих числовые значения, выглядящие как целые числа или числа с плавающей точкой, pandas комбинирует столбцы и хранит их в виде структуры данных ndarray библиотеки NumPy. Эта структура данных построена на основе массива C, значения хранятся в непрерывном блоке памяти. Благодаря такой схеме хранения данных доступ к фрагментам данных осуществляется очень быстро.

Так как данные разных типов хранятся раздельно, мы исследуем использование памяти разными типами данных. Начнём со среднего показателя использования памяти по разным типам данных.

for dtype in ['float','int','object']:
    selected_dtype = gl.select_dtypes(include=[dtype])
    mean_usage_b = selected_dtype.memory_usage(deep=True).mean()
    mean_usage_mb = mean_usage_b / 1024 ** 2
    print("Average memory usage for {} columns: {:03.2f} MB".format(dtype,mean_usage_mb))

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

Average memory usage for float columns: 1.29 MB
Average memory usage for int columns: 1.12 MB
Average memory usage for object columns: 9.53 MB

Эти сведения дают нам понять то, что большая часть памяти уходит на 78 столбцов, хранящих объектные значения. Мы ещё поговорим об этом позже, а сейчас давайте подумаем о том, можем ли мы улучшить использование памяти столбцами, хранящими числовые данные.

Подтипы

Как мы уже говорили, pandas представляет числовые значения в виде структур данных ndarray NumPy и хранит их в непрерывных блоках памяти. Эта модель хранения данных позволяет экономно расходовать память и быстро получать доступ к значениям. Так как pandas представляет каждое значение одного и того же типа, используя одинаковое число байт, и структуры ndarray хранят сведения о числе значений, pandas может быстро и точно выдать сведения об объёме памяти, потребляемых столбцами, хранящими числовые значения.

У многих типов данных в pandas есть множество подтипов, которые могут использовать меньшее число байт для представления каждого значения. Например тип float имеет подтипы float16, float32 и float64. Число в имени типа указывает на количество бит, которые подтип использует для представления значений. Например, в только что перечисленных подтипах для хранения данных используется, соответственно, 2, 4, 8 и 16 байт. В следующей таблице представлены подтипы наиболее часто используемых в pandas типов данных.

Использование памяти, байт Число с плавающей точкой Целое число Беззнаковое целое число Дата и время Логическое значение Объект
1 int8 uint8 bool
2 float16 int16 uint16
4 float32 int32 uint32
8 float64 int64 uint64 datetime64
Переменный объём памяти object


Значение типа int8 использует 1 байт (8 бит) для хранения числа и может представлять 256 двоичных значений (2 в 8 степени). Это означает, что этот подтип можно использовать для хранения значений в диапазоне от -128 до 127 (включая 0).

Для проверки минимального и максимального значения, подходящего для хранения с использованием каждого целочисленного подтипа, можно воспользоваться методом numpy.iinfo(). Рассмотрим пример:

import numpy as np
int_types = ["uint8", "int8", "int16"]
for it in int_types:
    print(np.iinfo(it))

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

Machine parameters for uint8
---------------------------------------------------------------
min = 0
max = 255
---------------------------------------------------------------

Machine parameters for int8
---------------------------------------------------------------
min = -128
max = 127
---------------------------------------------------------------

Machine parameters for int16
---------------------------------------------------------------
min = -32768
max = 32767
---------------------------------------------------------------

Тут можно обратить внимание на различие между типами uint (беззнаковое целое) и int (целое число со знаком). Оба типа имеют одинаковую ёмкость, но, при хранении в столбцах только положительных значений, беззнаковые типы позволяют эффективнее расходовать память.

Оптимизация хранения числовых данных с использованием подтипов

Функцию pd.to_numeric() можно использовать для нисходящего преобразования числовых типов. Для выбора целочисленных столбцов воспользуемся методом DataFrame.select_dtypes(), затем оптимизируем их и сравним использование памяти до и после оптимизации.

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


def mem_usage(pandas_obj):
    if isinstance(pandas_obj,pd.DataFrame):
        usage_b = pandas_obj.memory_usage(deep=True).sum()
    else: # исходим из предположения о том, что если это не DataFrame, то это Series
        usage_b = pandas_obj.memory_usage(deep=True)
    usage_mb = usage_b / 1024 ** 2 # преобразуем байты в мегабайты
    return "{:03.2f} MB".format(usage_mb)

gl_int = gl.select_dtypes(include=['int'])
converted_int = gl_int.apply(pd.to_numeric,downcast='unsigned')

print(mem_usage(gl_int))
print(mem_usage(converted_int))

compare_ints = pd.concat([gl_int.dtypes,converted_int.dtypes],axis=1)
compare_ints.columns = ['before','after']
compare_ints.apply(pd.Series.value_counts)

Вот что получается в результате исследования потребления памяти:

7.87 MB
1.48 MB

До После
uint8 NaN 5.0
uint32 NaN 1.0
int64 6.0 NaN

В результате можно видеть падение использования памяти с 7.9 до 1.5 мегабайт, то есть — мы снизили потребление памяти больше, чем на 80%. Общее воздействие этой оптимизации на исходный объект DataFrame, однако, не является особенно сильным, так как в нём очень мало целочисленных столбцов.

Сделаем то же самое со столбцами, содержащими числа с плавающей точкой.

gl_float = gl.select_dtypes(include=['float'])
converted_float = gl_float.apply(pd.to_numeric,downcast='float')

print(mem_usage(gl_float))
print(mem_usage(converted_float))

compare_floats = pd.concat([gl_float.dtypes,converted_float.dtypes],axis=1)
compare_floats.columns = ['before','after']
compare_floats.apply(pd.Series.value_counts)

В результате получается следующее:

100.99 MB
50.49 MB

До После
float32 NaN 77.0
float64 77.0 NaN

В результате все столбцы, хранившие числа с плавающей точкой с типом данных float64, теперь хранят числа типа float32, что дало нам 50% уменьшение использования памяти.

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

optimized_gl = gl.copy()

optimized_gl[converted_int.columns] = converted_int
optimized_gl[converted_float.columns] = converted_float

print(mem_usage(gl))
print(mem_usage(optimized_gl))

Вот что у нас получилось:

861.57 MB
804.69 MB

Хотя мы значительно уменьшили потребление памяти столбцами, хранящими числовые данные, в целом, по всему объекту DataFrame, потребление памяти снизилось лишь на 7%. Источником куда более серьёзного улучшения ситуации может стать оптимизация хранения объектных типов.

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

Сравнение механизмов хранения чисел и строк

Тип object представляет значения с использованием строковых объектов Python. Отчасти это так от того, что NumPy не поддерживает представление отсутствующих строковых значений. Так как Python — это высокоуровневый интерпретируемый язык, он не даёт программисту инструментов для тонкого управления тем, как данные хранятся в памяти.

Это ограничение ведёт к тому, что строки хранятся не в непрерывных фрагментах памяти, их представление в памяти фрагментировано. Это ведёт к увеличению потребления памяти и к замедлению скорости работы со строковыми значениями. Каждый элемент в столбце, хранящем объектный тип данных, на самом деле, представляет собой указатель, который содержит «адрес», по которому настоящее значение расположено в памяти.

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

Хранение числовых и строковых данных

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

Итак, сначала исследуем обычные строки:

from sys import getsizeof

s1 = 'working out'
s2 = 'memory usage for'
s3 = 'strings in python is fun!'
s4 = 'strings in python is fun!'

for s in [s1, s2, s3, s4]:
    print(getsizeof(s))

Здесь данные по использованию памяти выглядят так:

60
65
74
74

Теперь посмотрим на то, как выглядит использование строк в объекте Series:

obj_series = pd.Series(['working out',
                          'memory usage for',
                          'strings in python is fun!',
                          'strings in python is fun!'])
obj_series.apply(getsizeof)

Здесь мы получаем следующее:

0    60
1    65
2    74
3    74
dtype: int64

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

Оптимизация хранения данных объектных типов с использованием категориальных переменных

Категориальные переменные появились в pandas версии 0.15. Соответствующий тип, category, использует в своих внутренних механизмах, вместо исходных значений, хранящихся в столбцах таблицы, целочисленные значения. Pandas использует отдельный словарь, устанавливающий соответствия целочисленных и исходных значений. Такой подход полезен в тех случаях, когда столбцы содержат значения из ограниченного набора. Когда данные, хранящиеся в столбце, конвертируют в тип category, pandas использует подтип int, который позволяет эффективнее всего распорядиться памятью и способен представить все уникальные значения, встречающиеся в столбце.

Исходные данные и категориальные данные, использующие подтип int8

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

gl_obj = gl.select_dtypes(include=['object']).copy()
gl_obj.describe()

То, что у нас получилось, вы может найти в этой таблице, на листе Количество уникальных значений в столбцах.

Например, в столбце day_of_week, представляющем собой день недели, в который проводилась игра, имеется 171907 значений. Среди них всего 7 уникальных. В целом же, одного взгляда на этот отчёт достаточно для того, чтобы понять, что во многих столбцах для представления данных примерно 172000 игр используется довольно-таки мало уникальных значений.

Прежде чем мы займёмся полномасштабной оптимизацией, давайте выберем какой-нибудь один столбец, хранящий объектные данные, да хотя бы day_of_week, и посмотрим, что происходит внутри программы при преобразовании его в категориальный тип.

Как уже было сказано, в этом столбце содержится всего 7 уникальных значений. Для преобразования его в категориальный тип воспользуемся методом .astype().

dow = gl_obj.day_of_week
print(dow.head())

dow_cat = dow.astype('category')
print(dow_cat.head())

Вот что у нас получилось:

0    Thu
1    Fri
2    Sat
3    Mon
4    Tue
Name: day_of_week, dtype: object
0    Thu
1    Fri
2    Sat
3    Mon
4    Tue
Name: day_of_week, dtype: category
Categories (7, object): [Fri, Mon, Sat, Sun, Thu, Tue, Wed]

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

В следующем коде мы используем атрибут Series.cat.codes для того, чтобы выяснить то, какие целочисленные значения тип category использует для представления каждого из дней недели:

dow_cat.head().cat.codes

Нам удаётся выяснить следующее:

0    4
1    0
2    2
3    1
4    5
dtype: int8

Тут можно заметить то, что каждому уникальному значению назначено целочисленное значение, и то, что столбец теперь имеет тип int8. Здесь нет отсутствующих значений, но если бы это было так, для указания таких значений использовалось бы число -1.

Теперь давайте сравним потребление памяти до и после преобразования столбца day_of_week к типу category.

print(mem_usage(dow))
print(mem_usage(dow_cat))

Вот что тут получается:

9.84 MB
0.16 MB

Как видно, сначала потреблялось 9.84 мегабайт памяти, а после оптимизации — лишь 0.16 мегабайт, что означает 98% улучшение этого показателя. Обратите внимание на то, что работа с этим столбцом, вероятно, демонстрирует один из наиболее выгодных сценариев оптимизации, когда в столбце, содержащем примерно 172000 элементов, используется лишь 7 уникальных значений.

Хотя идея преобразования всех столбцов к этому типу данных выглядит привлекательно, прежде чем это делать, стоит учитывать негативные побочные эффекты такого преобразования. Так, наиболее серьёзный минус этого преобразования заключается в невозможности выполнения арифметических операций над категориальными данными. Это касается и обычных арифметических операций, и использования методов наподобие Series.min() и Series.max() без предварительного преобразования данных к настоящему числовому типу.

Нам стоит ограничить использование типа category, в основном, столбцами, хранящими данные типа object, в которых уникальными являются менее 50% значений. Если все значения в столбце уникальны, то использование типа category приведёт к повышению уровня использования памяти. Это происходит из-за того, что в памяти приходится хранить, в дополнение к числовым кодам категорий, ещё и исходные строковые значения. Подробности об ограничениях типа category можно почитать в документации к pandas.

Создадим цикл, который перебирает все столбцы, хранящие данные типа object, выясняет, не превышает ли число уникальных значений в столбцах 50%, и если это так, преобразует их в тип category.

converted_obj = pd.DataFrame()

for col in gl_obj.columns:
    num_unique_values = len(gl_obj[col].unique())
    num_total_values = len(gl_obj[col])
    if num_unique_values / num_total_values < 0.5:
        converted_obj.loc[:,col] = gl_obj[col].astype('category')
    else:
        converted_obj.loc[:,col] = gl_obj[col]

Теперь сравним то, что получилось после оптимизации, с тем, что было раньше:

print(mem_usage(gl_obj))
print(mem_usage(converted_obj))

compare_obj = pd.concat([gl_obj.dtypes,converted_obj.dtypes],axis=1)
compare_obj.columns = ['before','after']
compare_obj.apply(pd.Series.value_counts)

Получим следующее:

752.72 MB
51.67 MB

До После
object 78.0 NaN
category NaN 78.0

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

Как видно, объём памяти, необходимый для работы со столбцами, хранящими данные типа object, снизился с 752 мегабайт до 52 мегабайт, то есть на 93%. Теперь давайте посмотрим на то, как нам удалось оптимизировать потребление памяти по всему набору данных. Проанализируем то, на какой уровень использования памяти мы вышли, если сравнить то, что получилось, с исходным показателем в 891 мегабайт.

optimized_gl[converted_obj.columns] = converted_obj

mem_usage(optimized_gl)

Вот что у нас получилось:

'103.64 MB'

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

date = optimized_gl.date
print(mem_usage(date))
date.head()

В плане использования памяти здесь получается следующее:

0.66 MB

Вот сводка по данным:

0    18710504
1    18710505
2    18710506
3    18710508
4    18710509
Name: date, dtype: uint32

Можно вспомнить, что исходные данные были представлены в целочисленном виде и уже оптимизированы с использованием типа uint32. Из-за этого преобразование этих данных в тип datetime приведёт к удвоению потребления памяти, так как этот тип использует для хранения данных 64 бита. Однако в преобразовании данных к типу datetime, всё равно, есть смысл, так как это позволит нам легче выполнять анализ временных рядов.

Преобразование выполняется с использованием функции to_datetime(), параметр format которой указывает на то, что данные хранятся в формате YYYY-MM-DD.

optimized_gl['date'] = pd.to_datetime(date,format='%Y%m%d')

print(mem_usage(optimized_gl))
optimized_gl.date.head()

В результате получается следующее:

104.29 MB

Данные теперь выглядят так:

0   1871-05-04
1   1871-05-05
2   1871-05-06
3   1871-05-08
4   1871-05-09
Name: date, dtype: datetime64[ns]

Выбор типов при загрузке данных

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

К счастью, оптимальные типы данных для отдельных столбцов можно указать ещё до фактической загрузки данных. Функция pandas.read_csv() имеет несколько параметров, позволяющих это сделать. Так, параметр dtype принимает словарь, в котором присутствуют, в виде ключей, строковые имена столбцов, и в виде значений — типы NumPy.

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

dtypes = optimized_gl.drop('date',axis=1).dtypes

dtypes_col = dtypes.index
dtypes_type = [i.name for i in dtypes.values]

column_types = dict(zip(dtypes_col, dtypes_type))

# вместо вывода всех 161 элементов, мы
# возьмём 10 пар ключ/значение из словаря
# и аккуратно их выведем

preview = first2pairs = {key:value for key,value in list(column_types.items())[:10]}
import pprint
pp = pp = pprint.PrettyPrinter(indent=4)
pp.pprint(preview)
Вот что у нас получится:
{   'acquisition_info': 'category',
    'h_caught_stealing': 'float32',
    'h_player_1_name': 'category',
    'h_player_9_name': 'category',
    'v_assists': 'float32',
    'v_first_catcher_interference': 'float32',
    'v_grounded_into_double': 'float32',
    'v_player_1_id': 'category',
    'v_player_3_id': 'category',
    'v_player_5_id': 'category'}

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

Соответствующий код получается довольно-таки компактным:

read_and_optimized = pd.read_csv('game_logs.csv',dtype=column_types,parse_dates=['date'],infer_datetime_format=True)

print(mem_usage(read_and_optimized))
read_and_optimized.head()

В результате объём использования памяти выглядит так:

104.28 MB

Данные теперь выглядят так, как показано на листе Фрагмент оптимизированного набора данных в этой таблице.

Внешне таблицы, приведённые на листах Фрагмент оптимизированного набора данных и Фрагмент исходного набора данных, за исключением столбца с датами, выглядят одинаково, но это касается лишь их внешнего вида. Благодаря оптимизации использования памяти в pandas нам удалось снизить потребление памяти с 861.6 Мбайт до 104.28 Мбайт, получив впечатляющий результат экономии 88% памяти.

Анализ бейсбольных матчей

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

optimized_gl['year'] = optimized_gl.date.dt.year
games_per_day = optimized_gl.pivot_table(index='year',columns='day_of_week',values='date',aggfunc=len)
games_per_day = games_per_day.divide(games_per_day.sum(axis=1),axis=0)

ax = games_per_day.plot(kind='area',stacked='true')
ax.legend(loc='upper right')
ax.set_ylim(0,1)
plt.show()

Дни, в которые проводились игры

Как видно, до 1920-х годов игры редко проводились по воскресеньям, после чего, примерно в течение 50 лет, игры в этот день постепенно проводились всё чаще.

Кроме того, можно заметить, что распределение дней недели, в которые проводились игры последние 50 лет, является практически неизменным.

Теперь взглянем на то, как со временем менялась длительность игр.

game_lengths = optimized_gl.pivot_table(index='year', values='length_minutes')
game_lengths.reset_index().plot.scatter('year','length_minutes')
plt.show()

Длительность игр

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

Итоги

В этом материале мы обсудили особенности хранения данных разных типов в pandas, после чего воспользовались полученными знаниями для уменьшения объёма памяти, необходимого для хранения объекта DataFrame, почти на 90%. Для этого мы применили две простые методики:

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

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

Уважаемые читатели! Перевести эту статью нам порекомендовал наш читатель eugene_bb. Если вам известны какие-нибудь интересные материалы, которые стоит перевести — расскажите нам о них.

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-разработчик
Узнать больше

Pandas – это библиотека с открытым исходным кодом на Python. Она предоставляет готовые к использованию высокопроизводительные структуры данных и инструменты анализа данных.

  • Модуль Pandas работает поверх NumPy и широко используется для обработки и анализа данных.
  • NumPy – это низкоуровневая структура данных, которая поддерживает многомерные массивы и широкий спектр математических операций с массивами. Pandas имеет интерфейс более высокого уровня. Он также обеспечивает оптимизированное согласование табличных данных и мощную функциональность временных рядов.
  • DataFrame – это ключевая структура данных в Pandas. Это позволяет нам хранить и обрабатывать табличные данные, как двумерную структуру данных.
  • Pandas предоставляет богатый набор функций для DataFrame. Например, выравнивание данных, статистика данных, нарезка, группировка, объединение, объединение данных и т.д.

Содержание

  1. Установка и начало работы с Pandas
  2. Структуры данных
  3. DataFrame
  4. Импорт данных из CSV
  5. Проверка данных
  6. 1. Получение статистической сводки записей
  7. 2. Сортировка записей
  8. 3. Нарезка записей
  9. 4. Фильтрация данных
  10. 5. Переименование столбца
  11. 6. Сбор данных
  12. а. merge()
  13. b. Группировка
  14. c. Конкатенация
  15. Создание DataFrame, переход Dict в Series
  16. Выбор столбца, добавление и удаление
  17. Заключение

Для установки модуля Pandas вам потребуется Python 2.7 и выше.

Если вы используете conda, вы можете установить его, используя команду ниже.

conda install pandas

Если вы используете PIP, выполните команду ниже, чтобы установить модуль pandas.

pip3.7 install pandas

Модуль Pandas в Python

Чтобы импортировать Pandas и NumPy в свой скрипт Python, добавьте следующий фрагмент кода:

import pandas as pd
import numpy as np

Поскольку Pandas зависит от библиотеки NumPy, нам нужно импортировать эту зависимость.

Структуры данных

Модуль Pandas предоставляет 3 структуры данных, а именно:

  • Series: это одномерный массив неизменного размера, подобный структуре, имеющей однородные данные.
  • DataFrames: это двумерная табличная структура с изменяемым размером и неоднородно типизированными столбцами.
  • Panel: это трехмерный массив с изменяемым размером.

DataFrame

DataFrame – самая важная и широко используемая структура данных, а также стандартный способ хранения данных. Она содержит данные, выровненные по строкам и столбцам, как в таблице SQL или в базе данных электронной таблицы.

Мы можем либо жестко закодировать данные в DataFrame, либо импортировать файл CSV, файл tsv, файл Excel, таблицу SQL и т.д.

Мы можем использовать приведенный ниже конструктор для создания объекта DataFrame.

pandas.DataFrame(data, index, columns, dtype, copy)

Ниже приводится краткое описание параметров:

  • data – создать объект DataFrame из входных данных. Это может быть список, dict, series, Numpy ndarrays или даже любой другой DataFrame;
  • index – имеет метки строк;
  • columns – используются для создания подписей столбцов;
  • dtype – используется для указания типа данных каждого столбца, необязательный параметр;
  • copy – используется для копирования данных, если есть.

Есть много способов создать DataFrame. Мы можем создать объект из словарей или списка словарей. Мы также можем создать его из списка кортежей, CSV, файла Excel и т.д.

Давайте запустим простой код для создания DataFrame из списка словарей.

import pandas as pd
import numpy as np
df = pd.DataFrame({
    "State": ['Andhra Pradesh', 'Maharashtra', 'Karnataka', 'Kerala', 'Tamil Nadu'],
    "Capital": ['Hyderabad', 'Mumbai', 'Bengaluru', 'Trivandrum', 'Chennai'],
    "Literacy %": [89, 77, 82, 97,85],
    "Avg High Temp(c)": [33, 30, 29, 31, 32 ]
})
print(df)

Вывод:

Модуль Dataframe Python

Первый шаг – создать словарь. Второй шаг – передать словарь в качестве аргумента в метод DataFrame(). Последний шаг – распечатать DataFrame.

Как видите, DataFrame можно сравнить с таблицей, имеющей неоднородное значение. Кроме того, можно изменить размер.

Мы предоставили данные в виде карты, и ключи карты рассматриваются Pandas, как метки строк.

Индекс отображается в крайнем левом столбце и имеет метки строк. Заголовок столбца и данные отображаются в виде таблицы.

Также возможно создавать индексированные DataFrames. Это можно сделать, настроив параметр индекса.

Импорт данных из CSV

Мы также можем создать DataFrame, импортировав файл CSV. Файл CSV – это текстовый файл с одной записью данных в каждой строке. Значения в записи разделяются символом «запятая».

Pandas предоставляет полезный метод с именем read_csv() для чтения содержимого файла CSV.

Например, мы можем создать файл с именем «cities.csv», содержащий подробную информацию о городах Индии. Файл CSV хранится в том же каталоге, что и сценарии Python. Этот файл можно импортировать с помощью:

import pandas as pd
data =  pd.read_csv('cities.csv')
print(data)

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

Проверка данных

print(df.head(2))

Head

print(df.tail(1))

Tail

Точно так же print (df.dtypes) печатает типы данных.

Dtype

print (df.index) печатает index.

df.index

print (df.columns) печатает столбцы DataFrame.

df.columns

print (df.values) отображает значения таблицы.

df.value

1. Получение статистической сводки записей

print(df['Literacy %'].describe())

Функция df.describe()

Функция df.describe() отображает статистическую сводку вместе с типом данных.

2. Сортировка записей

print(df.sort_values('Literacy %', ascending=False))

Сортировка записей

3. Нарезка записей

df['Capital']
(df.Capital)

Нарезка записей

print(df[['State', 'Capital']])

Slicemultiplecol

df[0:3]

Slicerows

Интересной особенностью библиотеки Pandas является выбор данных на основе меток строк и столбцов с помощью функции iloc [0].

Часто для анализа может потребоваться всего несколько столбцов. Мы также можем выбрать по индексу, используя loc [‘index_one’]).

Например, чтобы выбрать вторую строку, мы можем использовать df.iloc [1 ,:].

Допустим, нам нужно выбрать второй элемент второго столбца. Это можно сделать с помощью функции df.iloc [1,1]. В этом примере функция df.iloc [1,1] отображает в качестве вывода «Мумбаи».

4. Фильтрация данных

print(df[df['Literacy %']>90])

Для фильтрации по условию можно использовать любой оператор сравнения.

Фильтрация данных

print(df[df['State'].isin(['Karnataka', 'Tamil Nadu'])])

Filter

5. Переименование столбца

df.rename(columns = {'Literacy %':'Literacy percentage'}, inplace=True)
print(df.head())

Аргумент inplace = True вносит изменения в DataFrame.

Переименование столбца

6. Сбор данных

Наука о данных включает в себя обработку данных, чтобы данные могли хорошо работать с алгоритмами данных. Data Wrangling – это процесс обработки данных, такой как слияние, группировка и конкатенация.

Библиотека Pandas предоставляет полезные функции, такие как merge(), groupby() и concat() для поддержки задач Data Wrangling.

import pandas as pd

d = {  
    'Employee_id': ['1', '2', '3', '4', '5'],
    'Employee_name': ['Akshar', 'Jones', 'Kate', 'Mike', 'Tina']
}
df1 = pd.DataFrame(d, columns=['Employee_id', 'Employee_name'])  
print(df1)

Сбор данных

import pandas as pd

data = {  
    'Employee_id': ['4', '5', '6', '7', '8'],
    'Employee_name': ['Meera', 'Tia', 'Varsha', 'Williams', 'Ziva']
}
df2 = pd.DataFrame(data, columns=['Employee_id', 'Employee_name'])  
print(df2)

Wrangling 2

а. merge()

print(pd.merge(df1, df2, on='Employee_id'))

Функция merge()

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

b. Группировка

import pandas as pd
import numpy as np

data = {
    'Employee_id': ['4', '5', '6', '7', '8'],
    'Employee_name': ['Meera', 'Meera', 'Varsha', 'Williams', 'Ziva']
}
df2 = pd.DataFrame(data)

group = df2.groupby('Employee_name')
print(group.get_group('Meera'))

Поле «Employee_name» со значением «Meera» сгруппировано по столбцу «Employee_name». Пример вывода приведен ниже:

Группировка

c. Конкатенация

print(pd.concat([df1, df2]))

Конкатенация

Создание DataFrame, переход Dict в Series

series_sample = pd.Series([100, 200, 300, 400])
print(series_sample)

Пример создания серии

Мы создали серию. Вы можете видеть, что отображаются 2 столбца. Первый столбец содержит значения индекса, начиная с 0. Второй столбец содержит элементы, переданные как серии.

Можно создать DataFrame, передав словарь Series. Давайте создадим DataFrame, который формируется путем объединения и передачи индексов ряда.

d = {'Matches played' : pd.Series([400, 300, 200], index=['Sachin', 'Kohli', 'Raina']),
'Position' : pd.Series([1, 2, 3, 4], index=['Sachin', 'Kohli', 'Raina', 'Dravid'])}
df = pd.DataFrame(d)
print(df)

Создание DataFrame

Для первой серии, поскольку мы не указали метку ‘d’, возвращается NaN.

Выбор столбца, добавление и удаление

d = {'Matches played' : pd.Series([400, 300, 200], index=['Sachin', 'Kohli', 'Raina']),
 'Position' : pd.Series([1, 2, 3, 4], index=['Sachin', 'Kohli', 'Raina', 'Dravid'])}
df = pd.DataFrame(d)
print(df['Matches played'])

Приведенный выше код печатает только столбец «Matches played» в DataFrame.

Выбор столбца

d = {'Matches played' : pd.Series([400, 300, 200], index=['Sachin', 'Kohli', 'Raina']),
 'Position' : pd.Series([1, 2, 3, 4], index=['Sachin', 'Kohli', 'Raina', 'Dravid'])}
df = pd.DataFrame(d)
df['Runrate']=pd.Series([80, 70, 60, 50], index=['Sachin', 'Kohli', 'Raina', 'Dravid'])
print(df)

Добавление

del df['Matches played']
df.pop('Matches played')

Удаление

Заключение

В этом руководстве у нас было краткое введение в библиотеку Pandas в Python. Мы также сделали практические примеры, чтобы раскрыть возможности библиотеки, используемой в области науки о данных. Мы также рассмотрели различные структуры данных в библиотеке Python.

На главную

Анализ данных на языке Python

  • Открытая наука и разработка программного обеспечения для исследований
  • Введение в pandas (на русском языке)
  • Уроки по работе с pandas (на русском языке)
  • Уроки по визуализации данных (на русском языке)
  • Кейсы и упражнения по pandas
  • Полезные модули и сервисы для анализа данных
  • Элементы науки о данных (на русском языке)
  • Введение в байесовскую статистику с использованием Python (на русском языке)
  • Модульные тесты для специалистов по данным (на русском языке)
  • Шпаргалки по pandas и Python
  • Учебники по анализу данных на Python (в оригинале)
  • Справочники и учебники по визуализации в Python (в оригинале)
  • Шпаргалки по Matplotlib (в оригинале)

Этапы анализа данных на языке Python:

01. Какие данные обрабатывает pandas? [HTML] [CoLab]
02. Как мне читать и записывать табличные данные? [HTML] [CoLab]
03. Как выбрать подмножество из DataFrame? [HTML] [CoLab]
04. Как строить график в pandas? [HTML] [CoLab]
05. Как создать новые столбцы? [HTML] [CoLab]
06. Как рассчитать сводную статистику? [HTML] [CoLab]
07. Как изменить раскладку таблиц? [HTML] [CoLab]
08. Как объединить данные из нескольких таблиц? [HTML] [CoLab]
09. Как легко обрабатывать данные временных рядов? [HTML] [CoLab]
10. Как манипулировать текстовыми данными? [HTML] [CoLab]

Уроки по работе с pandas на русском языке

Pandas за 10 минут [HTML] [CoLab] [original doc]
Типичные задачи Excel, продемонстрированные в pandas (часть 1) [HTML] [CoLab] [original]
Типичные задачи Excel, продемонстрированные в pandas (часть 2) [HTML] [CoLab] [original]
Советы по выбору столбцов в DataFrame с помощью iloc и filter [HTML] [CoLab] [original]
Обзор типов данных pandas [HTML] [CoLab] [original]
Использование типа данных категории в pandas [HTML] [CoLab] [original]
Руководство по кодированию категориальных значений в Python [HTML] [CoLab] [original]
Очистка данных о валюте с помощью pandas [HTML] [CoLab] [original]
Эффективная очистка текста с помощью pandas [HTML] [CoLab] [original]
Excel процедуры Filter и Edit, продемонстрированные в pandas [HTML] [CoLab] [original]
Сводная таблица (pivot_table) в pandas [HTML] [CoLab] [original]
Подробное руководство по группировке и агрегированию с помощью pandas [HTML] [CoLab] [original]
Объяснение функций Grouper и agg в pandas [HTML] [CoLab] [original]
Понимание функции transform в pandas [HTML] [CoLab] [original]
Объяснение кросс-таблицы (crosstab) в pandas [HTML] [CoLab] [original]
Биннинг (разделение) данных с помощью qcut и cut в pandas [HTML] [CoLab] [original]
Моделирование Монте-Карло с помощью Python [HTML] [CoLab] [original]
Аккуратные данные (tidy data) в Python [HTML] [CoLab] [original]

Уроки по визуализации данных на русском языке

Эффективное использование Matplotlib [HTML] [CoLab] [original]
Взгляд на Plotly Express [HTML] [CoLab] [original]
Введение в визуализацию данных с помощью Altair (часть 1) [HTML] [CoLab] [original] и [doc]
Введение в визуализацию данных с помощью Altair (часть 2) [HTML] [CoLab] [doc]
Введение в визуализацию данных с помощью Altair (часть 3) [HTML] [CoLab] [doc]
Делаем сетевые графы интерактивными с помощью pyvis [HTML] [CoLab] [original doc]
Визуализация с помощью HoloViz (hvplot) [HTML] [miniconda] [original doc]

Полезные модули и сервисы для анализа данных

Шаблон Cookiecutter на русском языке: позволяет автоматизировать создание структуры файлов и каталогов для проекта по анализу данных.

Создание простых сводных таблиц в pandas с помощью модуля sidetable [HTML] [CoLab]
Используем модуль folium для рисования карт [HTML] [CoLab]
Использование модуля Pandas Profiling для профилирования [HTML] [CoLab]
Проверка статистических данных с помощью модуля pandera [HTML] [CoLab]
Pandas Tutor визуализирует, как код Python преобразует DataFrames [сайт]

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

Исследовательская разработка программного обеспечения (перевод книги)

  1. Введение [HTML]
  2. Структура проекта [HTML]
  3. Создание инструментов с помощью командной оболочки [HTML] [CoLab]

Полезные материалы по открытой науке

  • Открытая наука как драйвер развития научно-исследовательской деятельности [HTML]
  • Textbook manifesto by Allen B. Downey [HTML]
  • Free Books, Why Not? by Allen B. Downey [HTML]
  • Research Software Engineering with Python [HTML] [GitHub]

Элементы науки о данных на русском языке (Elements Of Data Science, professor Allen Downey)

Введение в байесовскую статистику с использованием Python на русском языке (Bite Size Bayes, professor Allen Downey)

Законы вероятности (+упражнения) [HTML] [CoLab] [original (01)]
Теорема Байеса (+упражнения) [HTML] [CoLab] [original (02)]
Проблема с печеньками (+упражнения) [HTML] [CoLab] [original (03)]

Кейсы и упражнения по pandas

  • Пример очистки данных titanic с помощью apply в [HTML]
  • Задание про метод apply в pandas о классификации вин в [Colab]
  • Регулярные выражения в Python и pandas [CoLab]
  • Что добавляют в избранное на Ozon в 2020 году? [CoLab]
  • Что не находят на Ozon в 2020 году? [CoLab]
  • Вакансии и зарплаты в Тинькофф в 2019 году [CoLab]
  • Анализ статистики COVID-19 в мире [CoLab]
  • Анализ частоты запросов на Яндекс.Метрике с помощью модуля pymorphy2 [CoLab]

Шпаргалки по pandas и Python

  • Основные функции загрузки и выгрузки данных в Python и pandas [HTML]
  • Основные команды при работе с утилитой conda [PDF]
  • Типы данных и операции в классическом Python [PDF]
  • Операции над матрицами в NumPy [PDF]
  • Официальная шпаргалка по pandas [PDF]
  • Восхитительная шпаргалка по pandas [PDF]
  • Шпаргалка по pandas от Enthought [PDF]
  • Презентация по типу данных Series (на русском языке) [PDF]
  • Презентация по типу данных DataFrame (на русском языке) [PDF]
  • Категориальные данные (на русском языке) [PDF]
  • Временные ряды (на русском языке) [PDF]
  • Сводные таблицы (на русском языке) [PDF]
  • Агрегирование и группировка (на русском языке) [PDF]
  • Слияние и соединение (на русском языке) [PDF]
  • Объединение наборов данных (на русском языке) [PDF]
  • Иерархическая индексация (на русском языке) [PDF]
  • Приведение данных в порядок (на русском языке) [PDF]
  • Шпаргалка по Seaborn [PDF]
  • Шпаргалка по plotly [PDF]
  • Шпаргалка по git [PDF]
  • Шпаргалка по git от Atlassian [PDF]
  • Шпаргалка по git от GitHub Education [PDF]
  • Шпаргалка по регулярным выражениям на Python [PDF]
  • Шпаргалка по регулярным выражениям (на русском языке) [PDF]
  • Шпаргалка по командам IPython [Ipython-quick-ref-sheets]

Учебники по анализу данных на Python / bash в оригинале

  • Изучение науки о данных (Learning Data Science by Sam Lau, Joey Gonzalez, and Deb Nolan) [оригинал книги]
  • Справочник по науке о данных на Python (Python Data Science Handbook, Jake VanderPlas) [оригинал книги] [GitHub]
  • Книга Python для анализа данных, 3-издание (Wes McKinney) (Python for Data Analysis, 3E) [оригинал книги]
  • Elements of Data Science by Allen B. Downey [оригинал книги]
  • Scientific Computing in Python: Introduction to NumPy and Matplotlib by Sebastian Raschka [оригинал книги]
  • Книга интервью по науке о данных (The Datascience Interview Project) [оригинал книги]
  • Вычислительное и дедуктивное мышление: основы науки о данных (Data 8) (Computational and Inferential Thinking: The Foundations of Data Science) [оригинал книги]
  • Программирование для экономистов (Coding for Economists) [оригинал книги]
  • Программирование на Python для науки о данных (PY4DS) (Python Programming for Data Science) [оригинал книги]
  • Введение в экономическое моделирование и науку о данных (Introduction to Economic Modeling and Data Science) [оригинал книги]
  • The Turing Way project [оригинал книги]
  • Введение в культурную аналитику и Python (Introduction to Cultural Analytics & Python) [оригинал книги]
  • Введение в науку о Земле и окружающей среде (An Introduction to Earth and Environmental Data Science) [оригинал книги]
  • Введение в науку о данных о Земле (Introduction to Earth Data Science) [оригинал книги]
  • Воспроизводимая наука о данных с Python (Reproducible Data Science with Python) [оригинал книги]
  • Воспроизводимые и надежные рабочие процессы для науки о данных (Reproducible and Trustworthy Workflows for Data Science) [оригинал книги]
  • The Programming Historian [оригинал книги]
  • Наука о данных в командной строке (Data Science at the Command Line) [оригинал книги]
  • Воспроизводимая наука о данных. Инструменты воспроизводимых исследований [лекции на русском языке]

Справочники и учебники по визуализации в Python в оригинале

  • Изучите Streamlit [оригинал на сайте автора]
  • Картирование и визуализация данных с помощью Python [оригинал на сайте автора]
  • Графические библиотеки (автор José Carlos Soage) [оригинальный сайт]
  • Поваренная книга по визуализации данных на Python (Pandas, Matplotlib, Seaborn, Plotly Express) [оригинальный сайт]
  • Учебная программа по визуализации данных (Vega-Lite, Altair) [оригинальный сайт]
  • Данные в стране чудес [оригинальная статья]
  • Шаблоны проектирования информационных панелей [оригинальный сайт]

Шпаргалки по Matplotlib

  • Шпаргалка по Matplotlib [PDF]
  • Шпаргалка Быстрый старт 1 [PNG]
  • Шпаргалка Быстрый старт 2 [PNG]
  • Шпаргалка для начинающих [PNG]
  • Шпаргалка для продвинутых пользователей [PNG]

Источники данных

  • PySport [сайт проекта]

Модульные тесты для специалистов по данным на русском языке

  • Возможности pytest [блокнот в Colab]

Python и кибербезопасность

  • Блокноты переехали на [отдельную страницу]

Pandas — главная Pythonбиблиотека для анализа данных. Она быстрая и мощная: в ней можно работать с таблицами, в которых миллионы строк. Вместе с Марией Жаровой, ментором проекта на курсе по Data Science, рассказываем про команды, которые позволят начать работать с реальными данными.

Среда разработки

Pandas работает как в IDE (средах разработки), так и в облачных блокнотах для программирования. Как установить библиотеку в конкретную IDE, читайте тут. Мы для примера будем работать в облачной среде Google Colab. Она удобна тем, что не нужно ничего устанавливать на компьютер: файлы можно загружать и работать с ними онлайн, к тому же есть совместный режим для работы с коллегами. Про Colab мы писали в этом обзоре.

Анализ данных в Pandas

На сайте Google Colab сразу появляется экран с доступными блокнотами. Создадим новый блокнот:

Импортирование библиотеки

Pandas недоступна в Python по умолчанию. Чтобы начать с ней работать, нужно ее импортировать с помощью этого кода: import pandas as pd

pd — это распространенное сокращенное название библиотеки. Далее будем обращаться к ней именно так.

Загрузка данных

В качестве тренировочного набора данных будем использовать «Отчет об уровне счастья» в разных странах за 2019 год (World Happiness Report). Открыть его можно двумя способами.

1. Загрузить в сессионное хранилище:

И прочитать с помощью такой команды: df = pd.read_csv('WHR_2019.csv')

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

Это можно сделать через словарь и через преобразование вложенных списков (фактически таблиц).

Через словарь: my_df = pd.DataFrame({'id': [1, 2, 3], 'name': ['Bob', 'Alice', 'Scott'], 'age': [21, 15, 30]})

Через вложенные списки: df = pd.DataFrame([[1,'Bob', 21], [2,'Alice', 15], [3,'Scott', 30]], columns = ['id','name', 'age'])

Результаты будут эквивалентны.

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

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

Если нужно посмотреть на другое количество строк, оно указывается в скобках, например df.head(12). Последние строки фрейма выводятся методом .tail().

Также чтобы просто полностью красиво отобразить датасет, используется функция display(). По умолчанию в Jupyter Notebook, если написать имя переменной на последней строке какой-либо ячейки (даже без ключевого слова display), ее содержимое будет отображено.

display(df) #эквивалентно команде df, если это последняя строка ячейки

Размеры датасета

Количество строк и столбцов в датафрейме можно узнать, используя метод .shape: df.shape #покажет размеры сразу по двум осям df.shape[0] #размер по горизонтали - то есть количество строк df.shape[1] #размер по горизонтали - то есть количество столбцов

Переименование столбцов

Названия столбцов можно переименовать под себя с помощью команды rename: df.rename(columns = {'Overall rank':'Место в рейтинге', 'Country or region':'Страна или регион', 'Score':'Баллы', 'GDP per capita':'ВВП на душу населения', 'Social support':'Социальная поддержка', 'Healthy life expectancy':'Ожидаемая продолжительность здоровой жизни', 'Freedom to make life choices':'Свобода жизненных выборов', 'Generosity':'Щедрость', 'Perceptions of corruption':'Восприятие коррупции'}, inplace = True) df.head()

Характеристики датасета

Чтобы получить первичное представление о статистических характеристиках нашего датасета, достаточно этой команды: df.describe()

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

Еще одна команда показывает другую справку: сколько значений в каждом столбце (в нашем случае в столбцах нет пропущенных значений) и формат данных: df.info()

Работа с отдельными столбцами или строками

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

1. Сделать срез фрейма df[['Место в рейтинге', 'Ожидаемая продолжительность здоровой жизни']]

Срез можно сохранить в новой переменной: data_new = df[['Место в рейтинге', 'Ожидаемая продолжительность здоровой жизни']]

Теперь можно выполнить любое действие с этим сокращенным фреймом.

2. Использовать метод loc

Если столбцов очень много, можно использовать метод loc, который ищет значения по их названию: df.loc [:, 'Место в рейтинге':'Социальная поддержка']

В этом случае мы оставили все столбцы от Места в рейтинге до Социальной поддержки.

3. Использовать метод iloc

Если нужно вырезать одновременно строки и столбцы, можно сделать это с помощью метода iloc:df.iloc[0:100, 0:5]

Первый параметр показывает индексы строк, которые останутся, второй — индексы столбцов. Получаем такой фрейм:

В методе iloc значения в правом конце исключаются, поэтому последняя строка, которую мы видим, — 99.

4. Использовать метод tolist()

Можно выделить какой-либо столбец в отдельный список при помощи метода tolist(). Это упростит задачу, если необходимо извлекать данные из столбцов: df['Баллы'].tolist()

Часто бывает нужно получить в виде списка названия столбцов датафрейма. Это тоже можно сделать с помощью метода tolist(): df.columns.tolist()

Добавление новых строк и столбцов

В исходный датасет можно добавлять новые столбцы, создавая новые «признаки», как говорят в машинном обучении. Например, создадим столбец «Сумма», в который просуммируем значения колонок «ВВП на душу населения» и «Социальная поддержка» (сделаем это в учебных целях, практически суммирование этих показателей не имеет смысла): df['Сумма'] = df['ВВП на душу населения'] + df['Социальная поддержка']

Можно добавлять и новые строки: для этого нужно составить словарь с ключами — названиями столбцов. Если вы не укажете значения в каких-то столбцах, они по умолчанию заполнятся пустыми значениями NaN. Добавим еще одну страну под названием Country: new_row = {'Место в рейтинге': 100, 'Страна или регион': 'Country', 'Баллы': 100} df = df.append(new_row, ignore_index=True)

Важно: при добавлении новой строки методом .append() не забывайте указывать параметр ignore_index=True, иначе возникнет ошибка.

Иногда бывает полезно добавить строку с суммой, медианой или средним арифметическим) по столбцу. Сделать это можно с помощью агрегирующих (aggregate (англ.) — группировать, объединять) функций: sum(), mean(), median(). Для примера добавим в конце строку с суммами значений по каждому столбцу: df = df.append(df.sum(axis=0), ignore_index = True)

Удаление строк и столбцов

Удалить отдельные столбцы можно при помощи метода drop() — это целесообразно делать, если убрать нужно небольшое количество столбцов. df = df.drop(['Сумма'], axis = 1)

В других случаях лучше воспользоваться описанными выше срезами.

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

Соответственно, задав параметр axis = 0, можно удалить любую строку из датафрейма: для этого нужно написать ее номер в качестве первого аргумента в методе drop(). Удалим последнюю строчку (указываем ее индекс — это будет количество строк): df = df.drop(df.shape[0]-1, axis = 0)

Копирование датафрейма

Можно полностью скопировать исходный датафрейм в новую переменную. Это пригодится, если нужно преобразовать много данных и при этом работать не с отдельными столбцами, а со всеми данными: df_copied = df.copy()

Уникальные значения

Уникальные значения в какой-либо колонке датафрейма можно вывести при помощи метода .unique(): df['Страна или регион'].unique()

Чтобы дополнительно узнать их количество, можно воспользоваться функцией len():len(df['Страна или регион'].unique())

Подсчет количества значений

Отличается от предыдущего метода тем, что дополнительно подсчитывает количество раз, которое то или иное уникальное значение встречается в колонке, пишется как .value_counts(): df['Страна или регион'].value_counts()

Группировка данных

Некоторым обобщением .value_counts() является метод .groupby() — он тоже группирует данные какого-либо столбца по одинаковым значениям. Отличие в том, что при помощи него можно не просто вывести количество уникальных элементов в одном столбце, но и найти для каждой группы сумму / среднее значение / медиану по любым другим столбцам.

Рассмотрим несколько примеров. Чтобы они были более наглядными, округлим все значения в столбце «Баллы» (тогда в нем появятся значения, по которым мы сможем сгруппировать данные): df['Баллы_new'] = round(df['Баллы'])

1) Сгруппируем данные по новому столбцу баллов и посчитаем, сколько уникальных значений для каждой группы содержится в остальных столбцах. Для этого в качестве агрегирующей функции используем .count():df.groupby('Баллы_new').count()

Получается, что чаще всего страны получали 6 баллов (таких было 49):

2) Получим более содержательный для анализа данных результат — посчитаем сумму значений в каждой группе. Для этого вместо .count() используем sum():df.groupby('Баллы_new').sum()

3) Теперь рассчитаем среднее значение по каждой группе, в качестве агрегирующей функции в этом случае возьмем mean():df.groupby('Баллы_new').mean()

4) Рассчитаем медиану. Для этого пишем команду median():df.groupby('Баллы_new').median()

Это самые основные агрегирующие функции, которые пригодятся на начальном этапе работы с данными.

Вот пример синтаксиса, как можно сагрегировать значения по группам при помощи сразу нескольких функций: df_agg = df.groupby('Баллы_new').agg({ 'Баллы_new': 'count', 'Баллы_new': 'sum', 'Баллы_new': 'mean', 'Баллы_new': 'median' })

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

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

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

Разберемся на примере. Сгруппируем средние значения из столбца «Социальная поддержка» по баллам в рейтинге и значению ВВП на душу населения. В прошлом действии мы уже округлили значения баллов, теперь округлим и значения ВВП: df['ВВП_new'] = round(df['ВВП на душу населения'])

Теперь составим сводную таблицу: по горизонтали расположим сгруппированные значения из округленного столбца «ВВП» (ВВП_new), а по вертикали — округленные значения из столбца «Баллы» (Баллы_new). В ячейках таблицы будут средние значения из столбца «Социальная поддержка», сгруппированные сразу по этим двум столбцам: pd.pivot_table(df, index = ['Баллы_new'], columns = ['ВВП_new'], values = 'Социальная поддержка', aggfunc = 'mean')

Сортировка данных

Строки датасета можно сортировать по значениям любого столбца при помощи функции sort_values(). По умолчанию метод делает сортировку по убыванию. Например, отсортируем по столбцу значений ВВП на душу населения: df.sort_values(by = 'ВВП на душу населения').head()

Видно, что самые высокие ВВП совсем не гарантируют высокое место в рейтинге.

Чтобы сделать сортировку по убыванию, можно воспользоваться параметром ascending (от англ. «по возрастанию») = False: df.sort_values(by = 'ВВП на душу населения', ascending=False)

Фильтрация

Иногда бывает нужно получить строки, удовлетворяющие определенному условию; для этого используется «фильтрация» датафрейма. Условия могут быть самые разные, рассмотрим несколько примеров и их синтаксис:

1) Получение строки с конкретным значением какого-либо столбца (выведем строку из датасета для Норвегии): df[df['Страна или регион'] == 'Norway']

2) Получение строк, для которых значения в некотором столбце удовлетворяют неравенству. Выведем строки для стран, у которых «Ожидаемая продолжительность здоровой жизни» больше единицы: df[df['Ожидаемая продолжительность здоровой жизни'] > 1]

3) В условиях фильтрации можно использовать не только математические операции сравнения, но и методы работы со строками. Выведем строки датасета, названия стран которых начинаются с буквы F, — для этого воспользуемся методом .startswith(): df[df['Страна или регион'].str.startswith('F')]

4) Можно комбинировать несколько условий одновременно, используя логические операторы. Выведем строки, в которых значение ВВП больше 1 и уровень социальной поддержки больше 1,5: df[(df['ВВП на душу населения'] > 1) & (df['Социальная поддержка'] > 1.5)]

Таким образом, если внутри внешних квадратных скобок стоит истинное выражение, то строка датасета будет удовлетворять условию фильтрации. Поэтому в других ситуациях можно использовать в условии фильтрации любые функции/конструкции, возвращающие значения True или False.

Применение функций к столбцам

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

Рассмотрим пример: напишем функцию, которая преобразует все буквы в строке к нижнему регистру, и применим к столбцу стран и регионов: def my_lower(row): return row.lower() df['Страна или регион'].apply(lower)

Очистка данных

Это целый этап работы с данными при подготовке их к построению моделей и нейронных сетей. Рассмотрим основные приемы и функции.

1) Удаление дубликатов из датасета делается при помощи функции drop_duplucates(). По умолчанию удаляются только полностью идентичные строки во всем датасете, но можно указать в параметрах и отдельные столбцы. Например, после округления у нас появились дубликаты в столбцах «ВВП_new» и «Баллы_new», удалим их: df_copied = df.copy() df_copied.drop_duplicates(subset = ['ВВП_new', 'Баллы_new'])

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

Строки-дубликаты удаляются полностью, таким образом, их количество уменьшается. Чтобы заменить их на пустые, можно использовать параметр inplace = True.df_copied.drop_duplicates(subset = ['ВВП_new', 'Баллы_new'], inplace = True)

2) Для замены пропусков NaN на какое-либо значение используется функция fillna(). Например, заполним появившиеся после предыдущего пункта пропуски в последней строке нулями: df_copied.fillna(0)

3) Пустые строки с NaN можно и вовсе удалить из датасета, для этого используется функция dropna() (можно также дополнительно указать параметр inplace = True): df_copied.dropna()

Построение графиков

В Pandas есть также инструменты для простой визуализации данных.

1) Обычный график по точкам.

Построим зависимость ВВП на душу населения от места в рейтинге: df.plot(x = 'Место в рейтинге', y = 'ВВП на душу населения')

2) Гистограмма.

Отобразим ту же зависимость в виде столбчатой гистограммы: df.plot.hist(x = 'Место в рейтинге', y = 'ВВП на душу населения')

3) Точечный график.
df.plot.scatter(x = 'Место в рейтинге', y = 'ВВП на душу населения')

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

Сохранение датафрейма на компьютер

Сохраним наш датафрейм на компьютер: df.to_csv('WHR_2019.csv')

Теперь с ним можно работать и в других программах.

Блокнот с кодом можно скачать здесь (формат .ipynb).

Впервые в Pandas? Вы пришли в нужное место.

Эту статью можно использовать в качестве справочника по библиотеке python pandas. Мы рассмотрим основные функции, необходимые для загрузки / хранения файлов различных форматов, очистки наборов данных и управления ими. Мы также рассмотрим преобразование фреймов данных в несколько массивов, которые будут использоваться для передачи в модель машинного обучения библиотеки sklearn.

Давайте начнем.

Вы можете установить pandas, используя:

pip3 install pandas

И обычно импортируется так:

import pandas as pd

Структуры данных

В пандах есть две структуры данных:

  1. Серия

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

Пример:

In [2]: obj = pd.Series(['a','b','c','d'])
In [3]: obj
Out[3]:
0    a
1    b
2    c
3    d
dtype: object

Pandas автоматически присваивает индекс, начиная с 0. Вы можете вызвать конкретную строку с помощью obj.loc[index]. Перейдите в это для документации.

In [4]: obj.loc[0]
Out[4]: 'a'

2. DataFrame

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

Пример:

In [5]: df = pd.DataFrame({'c1':['a','b'],'c2':['c','d'],'c3':['e','f']})  #as a dict of series
In [6]: df
Out[6]:
       c1      c2      c3
0       a       c       e
1       b       d       f
In [10]: df.loc[1] #outputs a series datatype
Out[10]:
c1    b
c2    d
c3    f
Name: 1, dtype: object

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

In [8]: df = pd.DataFrame({'c1':['a','b'],'c2':['c','d'],'c3':['e','f']}, index=['zero','one'])
In [9]: df
Out[9]:
          c1      c2      c3
zero       a       c       e
one        b       d       f

Объект DataFrame может быть инициализирован множеством параметров. Перейдите в это для документации.

Фрейм данных Pandas имеет две оси. Строки — axis=0, столбцы — axis=1.

Чтение файлов / сохранение фреймов данных

Допустим, у вас есть файл данных. Он может быть любого формата (csv, xlsx, xls, json, html или sql-запрос). Pandas предоставляет функции для чтения всех типов файлов. Вы также можете читать данные с URL-адреса, указывающего на файл данных.

Точно так же вы также можете сохранить очищенные и обработанные данные в любом формате файла по вашему выбору.

file_type     read                          save
csv           pd.read_csv('file.csv')       df.to_csv('name.csv')
xlsx          pd.read_excel('file.xlsx')    df.to_excel('name.xlsx')  
xls           pd.read_excel('file.xls')     df.to_excel('name.xls')
json          pd.read_json('file.json')     df.to_json('name.json')
html          pd.read_html(html_file)       df.to_html(html_file)
sql           pd.read_sql(sql_query)        df.to_sql(sql)
              or corresponding file urls

Все вышеперечисленные функции имеют разные параметры при чтении. Для подробного описания параметра перейдите в это.

В этой статье мы будем использовать набор данных о характеристиках автомобиля, чтобы продемонстрировать использование Pandas. Давайте прочитаем CSV-файл.

df=pd.read_csv('cars.csv')
df.head()

Названия столбцов

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

df.columns

Pandas автоматически устанавливает имена столбцов в качестве первой строки набора данных, если для параметра header в функции чтения не указано значениеNone.

Проверка типов данных

Типы данных каждого столбца можно получить, используя:

df.dtypes

Вы также можете использовать:

df.summary()

Удаление дубликатов

Повторяющиеся строки можно удалить drop_duplicates методом:

df.drop_duplicates()

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

Уникальные значения:

Вы можете получить уникальные значения каждого столбца, используя df[column].unique(), а также получить счетчики, используя df[column].value_counts(). Посмотрим, как это работает.

df['Make'].unique()

df['Make'].value_counts()[:5] #get only top 5

Сортировка

Предположим, вы хотите отсортировать таблицу по highway MPG в порядке убывания, это можно сделать с помощью:

df.sort_values(by='highway MPG', ascending = False).head()

Условное извлечение

Допустим, вы хотите получить автомобили с расходом топлива на галлон больше 100 на шоссе. Это можно сделать с помощью:

df[df['highway MPG']>100].head(10)

Вы также можете добавить несколько условий. Предположим, вам нужны строки с highway MPG больше 100 Make Fiat. Это можно сделать с помощью:

df[(df['highway MPG']>100) & (df['Make']=='FIAT')]

За три года подряд появилось три автомобиля FIAT с одинаковой ценой и понижающейся ценой на галлон.

ПРИМЕЧАНИЕ. Эти методы не вносят изменений в исходный фрейм данных. Он просто извлекает отфильтрованные данные и отображает их. Вам необходимо назначить его обратно исходной переменной фрейма данных или новой переменной, чтобы использовать ее.

Удаление / фильтрация столбцов

Допустим, вам нужно удалить столбцы Engine Cylinders и Engine HP, это можно сделать с помощью:

df = df.drop(['Engine Cylinder', 'Engine HP'], axis=1)

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

df.drop(['Engine Cylinder', 'Engine HP'] , axis=1, inplace=True)

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

Вы также можете отбросить строки, введя соответствующие индексы строк и установив axis=0.

Теперь предположим, что вам нужно отбросить много столбцов, а вам нужны только Make, Model and Year столбцы, вы можете использовать метод drop или отфильтровать нужные столбцы следующим образом:

new_df = df[['Make','Model','Year']] 

Замена значений

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

>>> df['Vehicle Size'].unique()
array(['Compact', 'Midsize', 'Large'], dtype=object)

При передаче функций данных в модель машинного обучения все категориальные строковые значения должны быть преобразованы в числа (целые числа). Столбец Vehicle Size имеет 3 категории. Заменим их на 0, 1 and 2.

#create dict to replace values
key_val = {'Compact':0,'Midsize':1,'Large':2}
df['Vehicle Size'] = df['Vehicle Size'].replace(key_val)
#Or use the inplace parameter
df['Vehicle Size'].replace(key_val, inplace=True)

Отсутствующие значения

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

df.isna().sum()

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

#filling all missing values in dataframe
df.fillna(value)
#for filling values in a particular column
df[column].fillna(value, inplace=True)

Преобразование в Numpy

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

df.values

Это возвращает массив numpy с каждой строкой в ​​DataFrame в качестве элемента. Мы можем отфильтровать функции и цели по отдельности, так как они должны передаваться отдельно в модель машинного обучения.

X = df[list_of_feature_columns].values
y = df[target_column].values
model=some_model()
model.fit(X,y)

Заключение

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

Понравилась статья? Поделить с друзьями:
  • Милпразон для кошек купить в екатеринбурге инструкция
  • De longi magnifica ecam 22 инструкция
  • Philips satinelle эпилятор инструкция по применению
  • Zinc 50 mg natures bounty инструкция по применению
  • Преднизолон ампулы инструкция по применению цена внутримышечно взрослым