Пакет 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.
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:
- Избавиться от строк или столбцов с нулевыми значениями
- Заменить нулевые значения на ненулевые — метод, известный как импутация.
Давайте подсчитаем общее количество 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 это высокоуровневая 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, облачное решение для работы с данными, которое можно запустить в браузере на любом устройстве: десктопе, ноутбуке, планшете или даже смартфоне.
Каждая строчка кода на скриншоте — это одно действие, результат которого 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 #Выводим объект на экран.
Теперь выведем его на экран:
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 на экран.
Посмотрим на результат:
Мы видим таблицу, строки которой имеют индексы от 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), то табличная вёрстка потеряется. Попробуйте вывести данные двумя способами и посмотрите на результат.
На экране появилась вот такая таблица:
В верхней части таблицы мы видим названия столбцов: country (страна), broadband (средняя скорость интернета) и mobile (средняя скорость мобильного интернета). Слева указаны индексы — от 0 до 176. То есть всего у нас 177 строк. В нижней части таблицы Pandas отображает и эту информацию.
Выводить таблицу полностью необязательно. Для первого знакомства с данными достаточно показать пять первых или пять последних строк. Сделать это можно с помощью df.head() или df.tail() соответственно. В скобках можно указать число строк, которое требуется указать. По умолчанию параметр равен 5.
df.head()
Результат:
Так намного удобнее. Мы можем сразу увидеть названия столбцов и тип данных в столбцах. Также в некоторых ячейках мы видим значение NaN — к нему мы вернёмся позже.
Теперь нам надо изучить импортированные данные. Действовать будем пошагово.
Шаг 1. Проверяем тип данных в таблице. Это поможет понять, в каком виде представлена информация в датасете — а иногда и найти аномалии. Например, даты могут быть сохранены в виде строк, что неудобно для последующего анализа. Проверить это можно с помощью стандартного метода:
df.dtypes
На экране появится таблица с обозначением типа данных в каждом столбце:
Что мы видим:
- столбец country представляет собой тип object. Это тип данных для строковых и смешанных значений;
- столбцы broadband и mobile имеют тип данных float, то есть относятся к числам с плавающей точкой.
Шаг 2. Быстро оцениваем данные и делаем предварительные выводы. Сделать это можно очень просто: для этого в Pandas существует специальный метод describe(). Он показывает среднее со стандартным отклонением, максимальные, минимальные значения переменных и их разделение по квантилям.
Посмотрим на этот метод в деле:
df.describe()
Результат:
Пройдёмся по каждой строчке:
- 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()
Результат:
Теперь рейтинг стран другой — пятёрка лидеров поменялась (потому что мы отсортировали данные по другому значению). Мы выяснили, что самый быстрый мобильный интернет в ОАЭ.
Но есть нюанс. Если вернуться к первоначальной таблице, отсортированной по скорости проводного интернета, можно заметить, что у лидера — Монако — во втором столбце написано NaN. NaN в Python указывает на отсутствие данных. Поэтому мы не знаем скорость мобильного интернета в Монако из этого датасета и не можем сделать однозначный вывод о лидерах в мире мобильной связи.
Попробуем отфильтровать значения, убрав страны с неизвестной скоростью мобильного интернета, и посмотрим на худшие по показателю страны (если оставить NaN, он будет засорять «дно» таблицы и увидеть реальные значения по самому медленному мобильному интернету будет сложновато).
В Pandas существуют различные способы фильтрации для удаления NaN. Мы воспользуемся методом dropna(), который удаляет все строки с пропусками. Важно, что удаляется полностью строка, содержащая NaN, а не только ячейки с пропущенными значениями в столбце с пропусками.
df.dropna()
Результат:
Количество строк в датафрейме при удалении пустых данных уменьшилось до 136. Если вернуться ко второму шагу, то можно увидеть, что это соответствует количеству заполненных строк в столбце mobile в начальном датафрейме.
Сохраним результат в новый датафрейм и назовём его df_without_nan. Изначальный DataFrame стараемся не менять, так как он ещё может нам понадобиться.
df_without_nan = df.dropna()
Теперь отсортируем полученные результаты по столбцу mobile от меньшего к большему и посмотрим на страну с самым медленным мобильным интернетом:
df_without_nan.sort_values('mobile', ascending=True)
Результат:
Худший мобильный интернет в Афганистане с небольшим отставанием от Палестины и Венесуэлы.
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-разработчик
Узнать больше
A-143, 9th Floor, Sovereign Corporate Tower, Sector-136, Noida, Uttar Pradesh — 201305
feedback@geeksforgeeks.org