Глава 3. Обработка данных

3.0 Введение

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

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

# Load library
import pandas as pd

# Create URL
url = 'https://raw.githubusercontent.com/chrisalbon/sim_data/master/titanic.csv'

# Load data as a dataframe
dataframe = pd.read_csv(url)

# Show first 5 rows
dataframe.head(5)
Name PClass Age Sex Survived SexCode
0 Allen, Miss Elisabeth Walton 1st 29.00 female 1 1
1 Allison, Miss Helen Loraine 1st 2.00 female 0 1
2 Allison, Mr Hudson Joshua Creighton 1st 30.00 male 0 0
3 Allison, Mrs Hudson JC (Bessie Waldo Daniels) 1st 25.00 female 0 1
4 Allison, Master Hudson Trevor 1st 0.92 male 1 0

В этом DataFrame следует заметить три важные вещи.

Во-первых, в DataFrame каждая строка соответствует одному наблюдению (например, пассажиру), а каждый столбец соответствует одному признаку (пол, возраст и т. д.). Например, взглянув на первое наблюдение, мы можем увидеть, что мисс Элизабет Уолтон Аллен путешествовала первым классом, ей было 29 лет, она была женщиной и пережила катастрофу.

Во-вторых, каждый столбец содержит имя (например, Name, PClass, Age), а каждая строка содержит номер индекса (например, 0 для счастливой мисс Элизабет Уолтон Аллен). Мы будем использовать их для выбора и манипулирования наблюдениями и признаками.

В-третьих, два столбца, Sex и SexCode, содержат одну и ту же информацию в разных форматах. В Sex женщина обозначена строкой female, а в SexCode женщина обозначена целым числом 1. Мы хотим, чтобы все наши признаки были уникальными, и поэтому нам нужно будет удалить один из этих столбцов.

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

3.1 Создание DataFrame

Проблема

Вы хотите создать новый DataFrame.

Решение

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

# Load library
import pandas as pd

# Create a dictionary
dictionary = {
"Name": ['Jacky Jackson', 'Steven Stevenson'],
"Age": [38, 25],
"Driver": [True, False]
}

# Create DataFrame
dataframe = pd.DataFrame(dictionary)

# Show DataFrame
dataframe
Name Age Driver
0 Jacky Jackson 38 True
1 Steven Stevenson 25 False

Добавлять новые столбцы в любой DataFrame с использованием списка значений легко:

# Add a column for eye color
dataframe["Eyes"] = ["Brown", "Blue"]

# Show DataFrame
dataframe
Name Age Driver Eyes
0 Jacky Jackson 38 True Brown
1 Steven Stevenson 25 False Blue

Обсуждение

pandas предлагает бесконечное количество способов создания DataFrame. В реальном мире создание пустого DataFrame, а затем его заполнение почти никогда не происходит. Вместо этого наши DataFrames будут создаваться из реальных данных, которые мы загрузили из других источников (например, из CSV-файла или базы данных).

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

Проблема

Вы хотите просмотреть некоторые характеристики DataFrame.

Решение

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

# Load library
import pandas as pd

# Create URL
url = 'https://raw.githubusercontent.com/chrisalbon/sim_data/master/titanic.csv'

# Load data
dataframe = pd.read_csv(url)

# Show two rows
dataframe.head(2)
Name PClass Age Sex Survived SexCode
0 Allen, Miss Elisabeth Walton 1st 29.0 female 1 1
1 Allison, Miss Helen Loraine 1st 2.0 female 0 1

Мы также можем посмотреть количество строк и столбцов:

# Show dimensions
dataframe.shape
(1313, 6)

Мы можем получить описательные статистики для любых числовых столбцов с помощью describe:

# Show statistics
dataframe.describe()
Age Survived SexCode
count 756.000000 1313.000000 1313.000000
mean 30.397989 0.342727 0.351866
std 14.259049 0.474802 0.477734
min 0.170000 0.000000 0.000000
25% 21.000000 0.000000 0.000000
50% 28.000000 0.000000 0.000000
75% 39.000000 1.000000 1.000000
max 71.000000 1.000000 1.000000

Кроме того, метод

info
также может показать некоторую полезную информацию:

# Show info
dataframe.info()

RangeIndex: 1313 entries, 0 to 1312
Data columns (total 6 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Name 1313 non-null object
1 PClass 1313 non-null object
2 Age 756 non-null float64
3 Sex 1313 non-null object
4 Survived 1313 non-null int64
5 SexCode 1313 non-null int64
dtypes: float64(1), int64(2), object(3)
memory usage: 61.7+ KB

Обсуждение

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

В нашем решении мы используем игрушечный набор данных о пассажирах Титаника в его последнем рейсе. Используя head, мы можем посмотреть на первые несколько строк (пять по умолчанию) данных. Альтернативно, мы можем использовать tail для просмотра последних нескольких строк. С помощью shape мы можем увидеть, сколько строк и столбцов содержит наш DataFrame. И, наконец, с помощью describe мы можем увидеть некоторые базовые описательные статистики для любого числового столбца.

Стоит отметить, что сводные статистики не всегда рассказывают полную историю. Например, pandas рассматривает столбцы Survived и SexCode как числовые, потому что они содержат 1 и 0. Однако в данном случае числовые значения представляют категории. Например, если Survived равно 1, это означает, что пассажир пережил катастрофу. По этой причине некоторые из предоставленных сводных статистик не имеют смысла, например, стандартное отклонение столбца SexCode (индикатор пола пассажира).

3.3 Нарезка DataFrames

Проблема

Вам нужно выбрать отдельные данные или срезы DataFrame.

Решение

Используйте loc или iloc для выбора одной или нескольких строк или значений:

# Load library
import pandas as pd

# Create URL
url = 'https://raw.githubusercontent.com/chrisalbon/sim_data/master/titanic.csv'

# Load data
dataframe = pd.read_csv(url)

# Select first row
dataframe.iloc[0]
Name Allen, Miss Elisabeth Walton
PClass 1st
Age 29
Sex female
Survived 1
SexCode 1
Name: 0, dtype: object

Мы можем использовать

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

# Select four rows
dataframe.iloc[1:4]
Name PClass Age Sex Survived SexCode
1 Allison, Miss Helen Loraine 1st 2.0 female 0 1
2 Allison, Mr Hudson Joshua Creighton 1st 30.0 male 0 0
3 Allison, Mrs Hudson JC (Bessie Waldo Daniels) 1st 25.0 female 0 1

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

# Select three rows
dataframe.iloc[:4]
Name PClass Age Sex Survived SexCode
0 Allen, Miss Elisabeth Walton 1st 29.0 female 1 1
1 Allison, Miss Helen Loraine 1st 2.0 female 0 1
2 Allison, Mr Hudson Joshua Creighton 1st 30.0 male 0 0
3 Allison, Mrs Hudson JC (Bessie Waldo Daniels) 1st 25.0 female 0 1

DataFrames не обязательно должны иметь числовую индексацию. Мы можем установить индекс DataFrame на любое значение, которое уникально для каждой строки. Например, мы можем установить индекс в качестве имен пассажиров, а затем выбирать строки по имени:

# Set index
dataframe = dataframe.set_index(dataframe['Name'])

# Show row
dataframe.loc['Allen, Miss Elisabeth Walton']
Name Allen, Miss Elisabeth Walton
PClass 1st
Age 29
Sex female
Survived 1
SexCode 1
Name: Allen, Miss Elisabeth Walton, dtype: object

Обсуждение

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

  • loc полезен, когда индекс DataFrame является меткой (например, строкой).

  • iloc работает путем поиска позиции в DataFrame. Например, iloc[0] вернет первую строку независимо от того, является ли индекс целым числом или меткой.

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

3.4 Выбор строк на основе условий

Проблема

Вы хотите выбрать строки DataFrame на основе некоторого условия.

Решение

Это легко сделать в pandas. Например, если бы мы хотели выбрать всех женщин на Титанике:

# Load library
import pandas as pd

# Create URL
url = 'https://raw.githubusercontent.com/chrisalbon/sim_data/master/titanic.csv'

# Load data
dataframe = pd.read_csv(url)

# Show top two rows where column 'sex' is 'female'
dataframe[dataframe['Sex'] == 'female'].head(2)
Name PClass Age Sex Survived SexCode
0 Allen, Miss Elisabeth Walton 1st 29.0 female 1 1
1 Allison, Miss Helen Loraine 1st 2.0 female 0 1

Взгляните на формат этого решения.

dataframe['Sex'] == 'female'
— это наше условное выражение; обернув его в
dataframe[]
, мы говорим pandas «выбрать все строки в DataFrame, где значение
dataframe['Sex']
— 'female'». Эти условия приводят к серии булевых значений pandas.

Множественные условия также легко задать. Например, здесь мы выбираем все строки, где пассажир — женщина 65 лет или старше:

# Filter rows
dataframe[(dataframe['Sex'] == 'female') & (dataframe['Age'] >= 65)]
Name PClass Age Sex Survived SexCode
73 Crosby, Mrs Edward Gifford (Catherine Elizabet... 1st 69.0 female 1 1

Обсуждение

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

3.5 Сортировка значений

Проблема

Вам нужно отсортировать DataFrame по значениям в столбце.

Решение

Используйте функцию sort_values pandas:

# Load library
import pandas as pd

# Create URL
url = 'https://raw.githubusercontent.com/chrisalbon/sim_data/master/titanic.csv'

# Load data
dataframe = pd.read_csv(url)

# Sort the dataframe by Age
dataframe.sort_values(by=["Age"]).head(2)
Name PClass Age Sex Survived SexCode
763 Dean, Miss Elizabeth Gladys (Millvena) 3rd 0.17 female 1 1
751 Danbom, Master Gilbert Sigvard Emanuel 3rd 0.33 male 0 0

Обсуждение

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

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

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

Проблема

Вам нужно заменить значения в DataFrame.

Решение

replace pandas — это простой способ поиска и замены значений. Например, мы можем заменить любой экземпляр "female" в столбце Sex на "Woman":

# Load library
import pandas as pd

# Create URL
url = 'https://raw.githubusercontent.com/chrisalbon/sim_data/master/titanic.csv'

# Load data
dataframe = pd.read_csv(url)

# Replace values, show two rows
dataframe['Sex'].replace("female", "Woman").head(2)
0 Woman
1 Woman
Name: Sex, dtype: object

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

# Replace "female" and "male with "Woman" and "Man"
dataframe['Sex'].replace(["female", "male"], ["Woman", "Man"]).head(5)
0 Woman
1 Woman
2 Man
3 Woman
4 Man
Name: Sex, dtype: object

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

# Replace values, show two rows
dataframe.replace(1, "One").head(2)
Name PClass Age Sex Survived SexCode
0 Allen, Miss Elisabeth Walton 1st 29 female One One
1 Allison, Miss Helen Loraine 1st 2 female 0 One

replace также принимает регулярные выражения:

# Replace values, show two rows
dataframe.replace(r"1st", "First", regex=True).head(2)
Name PClass Age Sex Survived SexCode
0 Allen, Miss Elisabeth Walton First 29.0 female 1 1
1 Allison, Miss Helen Loraine First 2.0 female 0 1

Обсуждение

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

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

Проблема

Вы хотите переименовать столбец в pandas DataFrame.

Решение

Переименуйте столбцы с помощью метода rename:

# Load library
import pandas as pd

# Create URL
url = 'https://raw.githubusercontent.com/chrisalbon/sim_data/master/titanic.csv'

# Load data
dataframe = pd.read_csv(url)

# Rename column, show two rows
dataframe.rename(columns={'PClass': 'Passenger Class'}).head(2)
Name Passenger Class Age Sex Survived SexCode
0 Allen, Miss Elisabeth Walton 1st 29.0 female 1 1
1 Allison, Miss Helen Loraine 1st 2.0 female 0 1

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

# Rename columns, show two rows
dataframe.rename(columns={'PClass': 'Passenger Class', 'Sex': 'Gender'}).head(2)
Name Passenger Class Age Gender Survived SexCode
0 Allen, Miss Elisabeth Walton 1st 29.0 female 1 1
1 Allison, Miss Helen Loraine 1st 2.0 female 0 1

Обсуждение

Использование rename со словарем в качестве аргумента параметра columns является моим предпочтительным способом переименования столбцов, потому что он работает с любым количеством столбцов. Если мы хотим переименовать все столбцы сразу, этот полезный фрагмент кода создает словарь с именами старых столбцов в качестве ключей и пустыми строками в качестве значений:

# Load library
import collections

# Create dictionary
column_names = collections.defaultdict(str)

# Create keys
for name in dataframe.columns:
column_names[name]

# Show dictionary
column_names
defaultdict(str,
{'Age': '',
'Name': '',
'PClass': '',
'Sex': '',
'SexCode': '',
'Survived': ''})

3.8 Нахождение минимума, максимума, суммы, среднего и количества

Проблема

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

Решение

pandas поставляется с некоторыми встроенными методами для часто используемых описательных статистик, таких как min, max, mean, sum и count:

# Load library
import pandas as pd

# Create URL
url = 'https://raw.githubusercontent.com/chrisalbon/sim_data/master/titanic.csv'

# Load data
dataframe = pd.read_csv(url)

# Calculate statistics
print('Maximum:', dataframe['Age'].max())
print('Minimum:', dataframe['Age'].min())
print('Mean:', dataframe['Age'].mean())
print('Sum:', dataframe['Age'].sum())
print('Count:', dataframe['Age'].count())
Maximum: 71.0
Minimum: 0.17
Mean: 30.397989417989415
Sum: 22980.879999999997
Count: 756

Обсуждение

В дополнение к статистикам, использованным в решении, pandas предлагает дисперсию (var), стандартное отклонение (std), эксцесс (kurt), асимметрию (skew), стандартную ошибку среднего (sem), моду (mode), медиану (median), счетчик значений (value counts) и ряд других.

Кроме того, мы можем применить эти методы ко всему DataFrame:

# Show counts
dataframe.count()
Name 1313
PClass 1313
Age 756
Sex 1313
Survived 1313
SexCode 1313
dtype: int64

3.9 Нахождение уникальных значений

Проблема

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

Решение

Используйте unique для просмотра массива всех уникальных значений в столбце:

# Load library
import pandas as pd

# Create URL
url = 'https://raw.githubusercontent.com/chrisalbon/sim_data/master/titanic.csv'

# Load data
dataframe = pd.read_csv(url)

# Select unique values
dataframe['Sex'].unique()
array(['female', 'male'], dtype=object)

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

# Show counts
dataframe['Sex'].value_counts()
male 851
female 462
Name: Sex, dtype: int64

Обсуждение

Как unique, так и value_counts полезны для манипулирования и исследования категориальных столбцов. Очень часто в категориальных столбцах будут классы, которые необходимо обрабатывать на этапе обработки данных. Например, в наборе данных о Титанике PClass — это столбец, указывающий класс билета пассажира. На Титанике было три класса; однако, если мы используем value_counts, мы можем увидеть проблему:

# Show counts
dataframe['PClass'].value_counts()
3rd 711
1st 322
2nd 279
* 1
Name: PClass, dtype: int64

В то время как почти все пассажиры принадлежат к одному из трех классов, как и ожидалось, у одного пассажира класс *. Существует ряд стратегий для обработки такого типа проблем, которые мы рассмотрим в Главе 5, но пока просто поймите, что «дополнительные» классы распространены в категориальных данных и их не следует игнорировать.

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

# Show number of unique values
dataframe['PClass'].nunique()
4

3.10 Обработка пропущенных значений

Проблема

Вы хотите выбрать пропущенные значения в DataFrame.

Решение

isnull и notnull возвращают булевы значения, указывающие, отсутствует ли значение:

# Load library
import pandas as pd

# Create URL
url = 'https://raw.githubusercontent.com/chrisalbon/sim_data/master/titanic.csv'

# Load data
dataframe = pd.read_csv(url)

## Select missing values, show two rows
dataframe[dataframe['Age'].isnull()].head(2)
Name PClass Age Sex Survived SexCode
12 Aubert, Mrs Leontine Pauline 1st NaN female 1 1
13 Barkworth, Mr Algernon H 1st NaN male 1 0

Обсуждение

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

# Attempt to replace values with NaN
dataframe['Sex'] = dataframe['Sex'].replace('male', NaN)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
 in ()
1 # Attempt to replace values with NaN
----> 2 dataframe['Sex'] = dataframe['Sex'].replace('male', NaN)

NameError: name 'NaN' is not defined

Чтобы иметь полную функциональность с NaN, нам сначала нужно импортировать библиотеку NumPy:

# Load library
import numpy as np

# Replace values with NaN
dataframe['Sex'] = dataframe['Sex'].replace('male', np.nan)

Часто в наборе данных используется определенное значение для обозначения пропущенного наблюдения, например NONE, -999 или .. pandas' read_csv включает параметр, позволяющий указать значения, используемые для обозначения пропущенных значений:

# Load data, set missing values
dataframe = pd.read_csv(url, na_values=[np.nan, 'NONE', -999])

Мы также можем использовать функцию fillna pandas для импутации пропущенных значений в столбце. Здесь мы показываем места, где Age равно null, используя функцию isna, а затем заполняем эти значения средним возрастом пассажиров.

# Get a single null row
null_entry = dataframe[dataframe["Age"].isna()].head(1)
null_entry
Name PClass Age Sex Survived SexCode
12 Aubert, Mrs Leontine Pauline 1st NaN female 1 1
# Fill all null values with the mean Age of passengers
null_entry.fillna(dataframe["Age"].mean())
Name PClass Age Sex Survived SexCode
12 Aubert, Mrs Leontine Pauline 1st 30.397989 female 1 1

3.11 Удаление столбца

Проблема

Вы хотите удалить столбец из вашего DataFrame.

Решение

Лучший способ удалить столбец — использовать drop с параметром axis=1 (т.е. ось столбца):

# Load library
import pandas as pd

# Create URL
url = 'https://raw.githubusercontent.com/chrisalbon/sim_data/master/titanic.csv'

# Load data
dataframe = pd.read_csv(url)

# Delete column
dataframe.drop('Age', axis=1).head(2)
Name PClass Sex Survived SexCode
0 Allen, Miss Elisabeth Walton 1st female 1 1
1 Allison, Miss Helen Loraine 1st female 0 1

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

# Drop columns
dataframe.drop(['Age', 'Sex'], axis=1).head(2)
Name PClass Survived SexCode
0 Allen, Miss Elisabeth Walton 1st 1 1
1 Allison, Miss Helen Loraine 1st 0 1

Если у столбца нет имени (что иногда может случиться), вы можете удалить его по его индексу столбца, используя dataframe.columns:

# Drop column
dataframe.drop(dataframe.columns[1], axis=1).head(2)
Name Age Sex Survived SexCode
0 Allen, Miss Elisabeth Walton 29.0 female 1 1
1 Allison, Miss Helen Loraine 2.0 female 0 1

Обсуждение

drop — идиоматический метод удаления столбца. Альтернативным методом является del dataframe['Age'], который работает в большинстве случаев, но не рекомендуется из-за того, как он вызывается внутри pandas (подробности выходят за рамки этой книги).

Я рекомендую избегать использования аргумента inplace=True pandas. Многие методы pandas включают параметр inplace, который при значении True редактирует DataFrame напрямую. Это может привести к проблемам в более сложных конвейерах обработки данных, потому что мы обрабатываем DataFrame как изменяемые объекты (какими они технически являются). Я рекомендую рассматривать DataFrame как неизменяемые объекты. Например:

# Create a new DataFrame
dataframe_name_dropped = dataframe.drop(dataframe.columns[0], axis=1)

В этом примере мы не изменяем DataFrame dataframe, а вместо этого создаем новый DataFrame, который является измененной версией dataframe, названной dataframe_name_dropped. Если вы будете рассматривать ваши DataFrames как неизменяемые объекты, вы избавитесь от многих проблем в дальнейшем.

3.12 Удаление строки

Проблема

Вы хотите удалить одну или несколько строк из DataFrame.

Решение

Используйте булево условие для создания нового DataFrame, исключающего строки, которые вы хотите удалить:

# Load library
import pandas as pd

# Create URL
url = 'https://raw.githubusercontent.com/chrisalbon/sim_data/master/titanic.csv'

# Load data
dataframe = pd.read_csv(url)

# Delete rows, show first two rows of output
dataframe[dataframe['Sex'] != 'male'].head(2)
Name PClass Age Sex Survived SexCode
0 Allen, Miss Elisabeth Walton 1st 29.0 female 1 1
1 Allison, Miss Helen Loraine 1st 2.0 female 0 1

Обсуждение

Хотя технически вы можете использовать метод drop (например,

df.drop([0, 1], axis=0)
для удаления первых двух строк), более практичный метод — просто обернуть булево условие внутри
df[]
. Причина в том, что мы можем использовать мощь условных выражений для удаления либо одной строки, либо (что гораздо более вероятно) многих строк одновременно.

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

# Delete row, show first two rows of output
dataframe[dataframe['Name'] != 'Allison, Miss Helen Loraine'].head(2)
Name PClass Age Sex Survived SexCode
0 Allen, Miss Elisabeth Walton 1st 29.0 female 1 1
2 Allison, Mr Hudson Joshua Creighton 1st 30.0 male 0 0

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

# Delete row, show first two rows of output
dataframe[dataframe.index != 0].head(2)
Name PClass Age Sex Survived SexCode
1 Allison, Miss Helen Loraine 1st 2.0 female 0 1
2 Allison, Mr Hudson Joshua Creighton 1st 30.0 male 0 0

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

Проблема

Вы хотите удалить повторяющиеся строки из вашего DataFrame.

Решение

Используйте drop_duplicates, но будьте внимательны к параметрам:

# Load library
import pandas as pd

# Create URL
url = 'https://raw.githubusercontent.com/chrisalbon/sim_data/master/titanic.csv'

# Load data
dataframe = pd.read_csv(url)

# Drop duplicates, show first two rows of output
dataframe.drop_duplicates().head(2)
Name PClass Age Sex Survived SexCode
0 Allen, Miss Elisabeth Walton 1st 29.0 female 1 1
1 Allison, Miss Helen Loraine 1st 2.0 female 0 1

Обсуждение

Внимательный читатель заметит, что решение на самом деле не удалило ни одной строки:

# Show number of rows
print("Number Of Rows In The Original DataFrame:", len(dataframe))
print("Number Of Rows After Deduping:", len(dataframe.drop_duplicates()))
Number Of Rows In The Original DataFrame: 1313
Number Of Rows After Deduping: 1313

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

# Drop duplicates
dataframe.drop_duplicates(subset=['Sex'])
Name PClass Age Sex Survived SexCode
0 Allen, Miss Elisabeth Walton 1st 29.0 female 1 1
2 Allison, Mr Hudson Joshua Creighton 1st 30.0 male 0 0

Внимательно изучите предыдущий вывод: мы сказали drop_duplicates рассматривать только любые две строки с одинаковым значением для Sex как дубликаты и удалять их. Теперь у нас остался DataFrame всего из двух строк: один мужчина и одна женщина. Вы можете спросить, почему drop_duplicates решил сохранить эти две строки вместо двух других строк. Ответ в том, что drop_duplicates по умолчанию сохраняет первое вхождение дублирующейся строки и удаляет остальные. Мы можем контролировать это поведение, используя параметр keep:

# Drop duplicates
dataframe.drop_duplicates(subset=['Sex'], keep='last')
Name PClass Age Sex Survived SexCode
1307 Zabour, Miss Tamini 3rd NaN female 0 1
1312 Zimmerman, Leo 3rd 29.0 male 0 0

Связанный метод duplicated, который возвращает булеву серию, указывающую, является ли строка дубликатом или нет. Это хороший вариант, если вы не хотите просто удалять дубликаты.

dataframe.duplicated()
0 False
1 False
2 False
3 False
4 False
...
1308 False
1309 False
1310 False
1311 False
1312 False
Length: 1313, dtype: bool

3.14 Группировка строк по значениям

Проблема

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

Решение

groupby — одна из самых мощных функций в pandas:

# Load library
import pandas as pd

# Create URL
url = 'https://raw.githubusercontent.com/chrisalbon/sim_data/master/titanic.csv'

# Load data
dataframe = pd.read_csv(url)

# Group rows by the values of the column 'Sex', calculate mean
# of each group
dataframe.groupby('Sex').mean()
Age Survived SexCode
Sex
female 29.396424 0.666667 1.0
male 31.014338 0.166863 0.0

Обсуждение

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

Пользователи, впервые использующие groupby, часто пишут такую строку и путаются в том, что возвращается:

# Group rows
dataframe.groupby('Sex')

Почему это не вернуло что-то более полезное? Причина в том, что groupby нужно сочетать с некоторой операцией, которую мы хотим применить к каждой группе, такой как вычисление агрегированной статистики (например, среднее, медиана, сумма). Говоря о группировке, мы часто используем сокращение и говорим «группировать по полу», но это неполно. Чтобы группировка была полезной, нам нужно сгруппировать по чему-то, а затем применить функцию к каждой из этих групп:

# Group rows, count rows
dataframe.groupby('Survived')['Name'].count()
Survived
0 863
1 450
Name: Name, dtype: int64

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

Мы также можем сгруппировать по первому столбцу, а затем сгруппировать эту группировку по второму столбцу:

# Group rows, calculate mean
dataframe.groupby(['Sex','Survived'])['Age'].mean()
Sex Survived
female 0 24.901408
       1 30.867143
male 0 32.320780
       1 25.951875
Name: Age, dtype: float64

3.15 Группировка строк по времени

Проблема

Вам нужно сгруппировать отдельные строки по периодам времени.

Решение

Используйте resample для группировки строк по временным интервалам:

# Load libraries
import pandas as pd
import numpy as np

# Create date range
time_index = pd.date_range('06/06/2017', periods=100000, freq='30S')

# Create DataFrame
dataframe = pd.DataFrame(index=time_index)

# Create column of random values
dataframe['Sale_Amount'] = np.random.randint(1, 10, 100000)

# Group rows by week, calculate sum per week
dataframe.resample('W').sum()
Sale_Amount
2017-06-11 86423
2017-06-18 101045
2017-06-25 100867
2017-07-02 100894
2017-07-09 100438
2017-07-16 10297

Обсуждение

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

Необработанные данные выглядят так:

# Show three rows
dataframe.head(3)
Sale_Amount
2017-06-06 00:00:00 7
2017-06-06 00:00:30 2
2017-06-06 00:01:00 7

Обратите внимание, что дата и время каждой продажи являются индексом DataFrame; это потому, что resample требует, чтобы индекс был значениями, похожими на datetime.

Используя resample, мы можем сгруппировать строки по широкому диапазону периодов времени (смещений), а затем вычислить некоторую статистику для каждой временной группы:

# Group by two weeks, calculate mean
dataframe.resample('2W').mean()
Sale_Amount
2017-06-11 5.001331
2017-06-25 5.007738
2017-07-09 4.993353
2017-07-23 4.950481
# Group by month, count rows
dataframe.resample('M').count()
Sale_Amount
2017-06-30 72000
2017-07-31 28000

Вы можете заметить, что в двух выводах индекс datetime является датой, несмотря на то, что мы группируем по неделям и месяцам соответственно. Причина в том, что по умолчанию resample возвращает метку правой «границы» (последняя метка) временной группы. Мы можем контролировать это поведение, используя параметр label:

# Group by month, count rows
dataframe.resample('M', label='left').count()
Sale_Amount
2017-05-31 72000
2017-06-30 28000

См. также

  • List of pandas time offset aliases

3.16 Агрегирующие операции и статистики

Проблема

Вам нужно агрегировать операцию по каждому столбцу (или набору столбцов) в DataFrame.

Решение

Используйте метод agg pandas. Здесь мы можем легко получить минимальное значение каждого столбца:

# Load library
import pandas as pd

# Create URL
url = 'https://raw.githubusercontent.com/chrisalbon/sim_data/master/titanic.csv'

# Load data
dataframe = pd.read_csv(url)

# Get the minimum of every column
dataframe.agg("min")
Name Abbing, Mr Anthony
PClass *
Age 0.17
Sex female
Survived 0
SexCode 0
dtype: object

Иногда мы хотим применить определенные функции к определенным наборам столбцов:

# Mean Age, min and max SexCode
dataframe.agg({"Age":["mean"], "SexCode":["min", "max"]})
Age SexCode
mean 30.397989 NaN
min NaN 0.0
max NaN 1.0

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

# Number of people who survived and didn't survive in each class
dataframe.groupby(["PClass","Survived"]).agg({"Survived":["count"]}).reset_index()
PClass Survived
count
0 * 0 1
1 1st 0 129
2 1st 1 193
3 2nd 0 160
4 2nd 1 119
5 3rd 0 573
6 3rd 1 138

Обсуждение

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

См. также

  • pandas agg documentation

3.17 Циклическая обработка столбца

Проблема

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

Решение

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

# Load library
import pandas as pd

# Create URL
url = 'https://raw.githubusercontent.com/chrisalbon/sim_data/master/titanic.csv'

# Load data
dataframe = pd.read_csv(url)

# Print first two names uppercased
for name in dataframe['Name'][0:2]:
print(name.upper())
ALLEN, MISS ELISABETH WALTON
ALLISON, MISS HELEN LORAINE

Обсуждение

В дополнение к циклам (часто называемым циклами for), мы также можем использовать списковые включения:

# Show first two names uppercased
[name.upper() for name in dataframe['Name'][0:2]]
['ALLEN, MISS ELISABETH WALTON', 'ALLISON, MISS HELEN LORAINE']

Несмотря на соблазн вернуться к циклам for, более Pythonic-решение будет использовать метод apply pandas, описанный в следующем рецепте.

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

Проблема

Вы хотите применить некоторую функцию ко всем элементам столбца.

Решение

Используйте apply для применения встроенной или пользовательской функции к каждому элементу столбца:

# Load library
import pandas as pd

# Create URL
url = 'https://raw.githubusercontent.com/chrisalbon/sim_data/master/titanic.csv'

# Load data
dataframe = pd.read_csv(url)

# Create function
def uppercase(x):
return x.upper()

# Apply function, show two rows
dataframe['Name'].apply(uppercase)[0:2]
0 ALLEN, MISS ELISABETH WALTON
1 ALLISON, MISS HELEN LORAINE
Name: Name, dtype: object

Обсуждение

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

3.19 Применение функции к группам

Проблема

Вы сгруппировали строки с помощью groupby и хотите применить функцию к каждой группе.

Решение

Объедините groupby и apply:

# Load library
import pandas as pd

# Create URL
url = 'https://raw.githubusercontent.com/chrisalbon/sim_data/master/titanic.csv'

# Load data
dataframe = pd.read_csv(url)

# Group rows, apply function to groups
dataframe.groupby('Sex').apply(lambda x: x.count())
Name PClass Age Sex Survived SexCode
Sex
female 462 462 288 462 462 462
male 851 851 468 851 851 851

Обсуждение

В Рецепте 3.18 я упомянул apply. apply особенно полезен, когда вы хотите применить функцию к группам. Объединив groupby и apply, мы можем вычислить пользовательские статистики или применить любую функцию к каждой группе отдельно.

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

Проблема

Вы хотите объединить два DataFrames.

Решение

Используйте concat с axis=0 для конкатенации вдоль оси строк:

# Load library
import pandas as pd

# Create DataFrame
data_a = {'id': ['1', '2', '3'],
'first': ['Alex', 'Amy', 'Allen'],
'last': ['Anderson', 'Ackerman', 'Ali']}
dataframe_a = pd.DataFrame(data_a, columns = ['id', 'first', 'last'])

# Create DataFrame
data_b = {'id': ['4', '5', '6'],
'first': ['Billy', 'Brian', 'Bran'],
'last': ['Bonder', 'Black', 'Balwner']}

# Concatenate DataFrames by rows
pd.concat([dataframe_a, dataframe_b], axis=0)
id first last
0 1 Alex Anderson
1 2 Amy Ackerman
2 3 Allen Ali
0 4 Billy Bonder
1 5 Brian Black
2 6 Bran Balwner

Вы можете использовать axis=1 для конкатенации вдоль оси столбцов:

# Concatenate DataFrames by columns
pd.concat([dataframe_a, dataframe_b], axis=1)
id first last id first last
0 1 Alex Anderson 4 Billy Bonder
1 2 Amy Ackerman 5 Brian Black
2 3 Allen Ali 6 Bran Balwner

Обсуждение

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

3.21 Слияние DataFrames

Проблема

Вы хотите объединить два DataFrames.

Решение

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

# Load library
import pandas as pd

# Create DataFrame
employee_data = {'employee_id': ['1', '2', '3', '4'],
'name': ['Amy Jones', 'Allen Keys', 'Alice Bees',
'Tim Horton']}
dataframe_employees = pd.DataFrame(employee_data, columns = ['employee_id',
'name'])

# Create DataFrame
sales_data = {'employee_id': ['3', '4', '5', '6'],
'total_sales': [23456, 2512, 2345, 1455]}
dataframe_sales = pd.DataFrame(sales_data, columns = ['employee_id',
'total_sales'])

# Merge DataFrames
pd.merge(dataframe_employees, dataframe_sales, on='employee_id')
employee_id name total_sales
0 3 Alice Bees 23456
1 4 Tim Horton 2512

merge по умолчанию выполняет внутренние объединения. Если мы хотим выполнить внешнее объединение, мы можем указать это с помощью параметра how:

# Merge DataFrames
pd.merge(dataframe_employees, dataframe_sales, on='employee_id', how='outer')
employee_id name total_sales
0 1 Amy Jones NaN
1 2 Allen Keys NaN
2 3 Alice Bees 23456.0
3 4 Tim Horton 2512.0
4 5 NaN 2345.0
5 6 NaN 1455.0

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

# Merge DataFrames
pd.merge(dataframe_employees, dataframe_sales, on='employee_id', how='left')
employee_id name total_sales
0 1 Amy Jones NaN
1 2 Allen Keys NaN
2 3 Alice Bees 23456.0
3 4 Tim Horton 2512.0

Мы также можем указать имя столбца в каждом DataFrame для объединения:

# Merge DataFrames
pd.merge(dataframe_employees,
dataframe_sales,
left_on='employee_id',
right_on='employee_id')
employee_id name total_sales
0 3 Alice Bees 23456
1 4 Tim Horton 2512

Если вместо объединения по двум столбцам мы хотим объединить по индексам каждого DataFrame, мы можем заменить параметры left_on и right_on на right_index=True и left_index=True.

Обсуждение

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

Этот процесс может быть знаком тем, кто использовал SQL, популярный язык для выполнения операций слияния (называемых joins). Хотя точные параметры, используемые pandas, будут отличаться, они следуют тем же общим шаблонам, которые используются в других языках и инструментах программирования.

Существует три аспекта, которые необходимо указать при любой операции слияния. Во-первых, мы должны указать два DataFrame, которые мы хотим объединить. В решении мы назвали их dataframe_employees и dataframe_sales. Во-вторых, мы должны указать имена столбцов, по которым будет выполняться слияние — то есть столбцов, значения которых являются общими для двух DataFrames. Например, в нашем решении оба DataFrame имеют столбец с именем employee_id. Для объединения двух DataFrames мы будем сопоставлять значения в столбце employee_id каждого DataFrame друг с другом. Если эти два столбца имеют одно и то же имя, мы можем использовать параметр on. Однако, если у них разные имена, мы можем использовать left_on и right_on.

Что такое левый и правый DataFrame? Простой ответ заключается в том, что левый DataFrame — это тот, который мы указали первым в merge, а правый DataFrame — второй. Эта терминология снова появится в следующих наборах параметров, которые нам понадобятся.

Последний аспект, который для некоторых людей наиболее сложен для понимания, — это тип операции слияния, которую мы хотим провести. Это указывается параметром how. merge поддерживает четыре основных типа объединений:

  • Inner

    Вернуть только строки, которые совпадают в обоих DataFrames (например, вернуть любую строку со значением employee_id, присутствующим как в dataframe_employees, так и в dataframe_sales).

  • Outer

    Вернуть все строки в обоих DataFrames. Если строка существует в одном DataFrame, но не в другом, заполнить значения NaN для отсутствующих значений (например, вернуть все строки как из dataframe_employee, так и из dataframe_sales).

  • Left

    Вернуть все строки из левого DataFrame, но только строки из правого DataFrame, которые совпали с левым DataFrame. Заполнить значения NaN для отсутствующих значений (например, вернуть все строки из dataframe_employees, но только строки из dataframe_sales, которые имеют значение employee_id, появляющееся в dataframe_employees).

  • Right

    Вернуть все строки из правого DataFrame, но только строки из левого DataFrame, которые совпали с правым DataFrame. Заполнить значения NaN для отсутствующих значений (например, вернуть все строки из dataframe_sales, но только строки из dataframe_employees, которые имеют значение employee_id, появляющееся в dataframe_sales).

Если вы не поняли всего этого прямо сейчас, я рекомендую вам поиграть с параметром how в вашем коде и посмотреть, как он влияет на то, что возвращает merge.

См. также

  • A Visual Explanation of SQL Joins

  • pandas documentation on merging

Предыдущая глава    Следующая глава

Другие статьи по этой теме: