В процессе анализа данных важно убедиться, что вы используете правильные типы данных; в противном случае можете получить неожиданные результаты или ошибки. В этой статье будут обсуждаться основные типы данных pandas (также известные как dtypes
), их сопоставление с типами данных Python и NumPy, а также варианты преобразования.
Оригинал статьи Криса тут.
Тип данных - это, по сути, внутреннее представление, которое язык программирования использует для понимания того, как данные хранить и как ими оперировать. Например, программа должна понимать, что вы хотите сложить два числа, например 5 + 10
, чтобы получить 15
. Или, если у вас есть две строки, такие как "кошка"
и "шляпа"
вы можете объединить (сложить) их вместе, чтобы получить "кошкашляпа"
.
Проблема с типами данных pandas заключается в том, что между pandas, Python и NumPy существует некоторое совпадение.
В следующей таблице приведены основные ключевые моменты:
Pandas | Python | NumPy | Использование |
---|---|---|---|
object | str или смесь | string, unicode, смешанные типы | Текстовые или смешанные числовые и нечисловые значения |
int64 | int | int_, int8, int16, int32, int64, uint8, uint16, uint32, uint64 | Целые числа |
float64 | float | float_, float16, float32, float64 | Числа с плавающей точкой |
bool | bool | bool_ | Значения True/False |
datetime64 | datetime | datetime64[ns] | Значения даты и времени |
timedelta[ns] | NA | NA | Разность между двумя datetimes |
category | NA | NA | Ограниченный список текстовых значений |
В этом Блокноте я сосредоточусь на следующих типах данных pandas:
object
int64
float64
datetime64
bool
Про тип category
смотрите в отдельной статье.
Тип данных object
может фактически содержать несколько разных типов. Например, столбец a
может включать целые числа, числа с плавающей точкой и строки, которые вместе помечаются как object
. Следовательно, вам могут потребоваться некоторые дополнительные методы для обработки смешанных типов данных.
В этой статье (а тут перевод статьи на русский язык) вы найдете инструкцию по очистке данных, представленных ниже.
Типы данных - одна из тех вещей, о которых вы, как правило, не заботитесь, пока не получите ошибку или неожиданные результаты. Это также одна из первых вещей, которую вы должны проверить после загрузки новых данных в pandas для дальнейшего анализа.
Я буду использовать очень простой CSV файл, чтобы проиллюстрировать пару распространенных ошибок, которые вы можете встретить.
import pandas as pd
import numpy as np
df = pd.read_csv("https://github.com/dm-fedorov/pandas_basic/blob/master/%D0%B1%D1%8B%D1%81%D1%82%D1%80%D0%BE%D0%B5%20%D0%B2%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%B2%20pandas/data/sales_data_types.csv?raw=True")
df
Customer Number | Customer Name | 2016 | 2017 | Percent Growth | Jan Units | Month | Day | Year | Active | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 10002.0 | Quest Industries | $125,000.00 | $162500.00 | 30.00% | 500 | 1 | 10 | 2015 | Y |
1 | 552278.0 | Smith Plumbing | $920,000.00 | $101,2000.00 | 10.00% | 700 | 6 | 15 | 2014 | Y |
2 | 23477.0 | ACME Industrial | $50,000.00 | $62500.00 | 25.00% | 125 | 3 | 29 | 2016 | Y |
3 | 24900.0 | Brekke LTD | $350,000.00 | $490000.00 | 4.00% | 75 | 10 | 27 | 2015 | Y |
4 | 651029.0 | Harbor Co | $15,000.00 | $12750.00 | -15.00% | Closed | 2 | 2 | 2014 | N |
На первый взгляд данные выглядят нормально, поэтому попробуем выполнить некоторые операции.
Сложим продажи за 2016
и 2017
годы:
df['2016'] + df['2017']
0 $125,000.00$162500.00 1 $920,000.00$101,2000.00 2 $50,000.00$62500.00 3 $350,000.00$490000.00 4 $15,000.00$12750.00 dtype: object
Выглядит странно. Мы хотели суммировать значения столбцов, но pandas их объединил, чтобы создать одну длинную строку.
Ключ к разгадке проблемы - это строка, в которой написано dtype: object
.
object
- это строка в pandas, поэтому он выполняет строковую конкатенацию вместо математического сложения.
Если мы хотим увидеть все типы данных, которые находятся в кадре данных (DataFrame
), то воспользуемся атрибутом dtypes
:
df.dtypes
Customer Number float64 Customer Name object 2016 object 2017 object Percent Growth object Jan Units object Month int64 Day int64 Year int64 Active object dtype: object
Кроме того, функция df.info()
показывает много полезной информации:
df.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 5 entries, 0 to 4 Data columns (total 10 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Customer Number 5 non-null float64 1 Customer Name 5 non-null object 2 2016 5 non-null object 3 2017 5 non-null object 4 Percent Growth 5 non-null object 5 Jan Units 5 non-null object 6 Month 5 non-null int64 7 Day 5 non-null int64 8 Year 5 non-null int64 9 Active 5 non-null object dtypes: float64(1), int64(3), object(6) memory usage: 528.0+ bytes
После просмотра автоматически назначаемых типов данных возникает несколько проблем:
Customer Number
(Номер клиента) - float64
, но должен быть int64
.2016
и 2017
хранятся как objects
, а не числовые значения, такие как float64
или int64
.Percent Growth
(Единицы процентного роста) и Jan Units
также хранятся как objects
, а не числовые значения.Month
, Day
и Year
, которые нужно преобразовать в datetime64
.Active
должен быть логическим (boolean
).Без проведения очистки данных будет сложно провести дополнительный анализ.
Чтобы преобразовать типы данных в pandas, есть три основных способа:
- Используйте метод
astype()
, чтобы принудительно задать тип данных.- Создайте настраиваемую (custom) функцию для преобразования данных.
- Используйте функции
to_numeric()
илиto_datetime()
.
df['Customer Number'].astype('int') # pandas понимает, что в итоге нужен int64
0 10002 1 552278 2 23477 3 24900 4 651029 Name: Customer Number, dtype: int64
Чтобы изменить Customer Number
в исходном кадре данных, обязательно присвойте его обратно столбцу, так как функция astype()
возвращает копию:
df["Customer Number"] = df['Customer Number'].astype('int')
df.dtypes
Customer Number int64 Customer Name object 2016 object 2017 object Percent Growth object Jan Units object Month int64 Day int64 Year int64 Active object dtype: object
А вот новый кадр данных с Customer Number
в качестве целого числа:
df
Customer Number | Customer Name | 2016 | 2017 | Percent Growth | Jan Units | Month | Day | Year | Active | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 10002 | Quest Industries | $125,000.00 | $162500.00 | 30.00% | 500 | 1 | 10 | 2015 | Y |
1 | 552278 | Smith Plumbing | $920,000.00 | $101,2000.00 | 10.00% | 700 | 6 | 15 | 2014 | Y |
2 | 23477 | ACME Industrial | $50,000.00 | $62500.00 | 25.00% | 125 | 3 | 29 | 2016 | Y |
3 | 24900 | Brekke LTD | $350,000.00 | $490000.00 | 4.00% | 75 | 10 | 27 | 2015 | Y |
4 | 651029 | Harbor Co | $15,000.00 | $12750.00 | -15.00% | Closed | 2 | 2 | 2014 | N |
Все это выглядит хорошо и кажется довольно простым.
Давайте попробуем проделать то же самое со столбцом 2016
и преобразовать его в число с плавающей точкой:
# здесь появится исключение:
# df['2016'].astype('float')
Аналогичным образом мы можем попытаться преобразовать столбец Jan Units
в целое число:
# здесь тоже появится исключение:
# df['Jan Units'].astype('int')
Оба примера возвращают исключения ValueError
, т.е. преобразования не сработали.
В каждом из случаев данные включали значения, которые нельзя было интерпретировать как числа. В столбцах продаж данные включают символ валюты $
, а также запятую. В столбце Jan Units
последним значением является Closed
(Закрыто), которое не является числом; так что мы получаем исключение.
Пока что astype()
как инструмент для преобразования выглядит не очень хорошо.
Мы должны попробовать еще раз в столбце Active
.
df['Active'].astype('bool')
0 True 1 True 2 True 3 True 4 True Name: Active, dtype: bool
На первый взгляд все выглядит нормально, но при ближайшем рассмотрении обнаруживается проблема. Все значения были интерпретированы как True
, но последний клиент в столбце Active
имеет флаг N
вместо Y
.
Вывод из этого раздела такой - astype()
будет работать, если:
astype('str')
.Если данные содержат нечисловые символы или неоднородны, то astype()
будет плохим выбором для преобразования типов. Вам потребуется выполнить дополнительные преобразования, чтобы изменение типа работало правильно.
Отметим, что astype()
может принимать словарь имен столбцов и типов данных:
df.astype({'Customer Number': 'int', 'Customer Name': 'str'}).dtypes
Customer Number int64 Customer Name object 2016 object 2017 object Percent Growth object Jan Units object Month int64 Day int64 Year int64 Active object dtype: object
Поскольку эти данные немного сложнее преобразовать, можно создать настраиваемую (custom) функцию, которую применим к каждому значению и преобразовать в соответствующий тип данных.
Для конвертации валюты (этого конкретного набора данных) мы можем использовать простую функцию:
def convert_currency(val):
"""
Преобразует числовое значение строки в число с плавающей точкой:
- удаляет $
- удаляет запятые
- преобразует в число с плавающей точкой
"""
new_val = val.replace(',', '').replace('$', '')
return float(new_val)
В коде используются строковые функции Python, чтобы очистить символы $
и ,
, а затем преобразовать значение в число с плавающей точкой. В этом конкретном случае мы могли бы преобразовать значения в целые числа, но я предпочитаю использовать плавающую точку.
Я также подозреваю, что кто-нибудь рекомендует использовать тип данных Decimal
для валюты. Это не встроенный тип в pandas, поэтому я намеренно придерживаюсь подхода с плавающей точкой.
Также следует отметить, что функция преобразует число в питоновский float
, но pandas внутренне преобразует его в float64
. Как упоминалось ранее, я рекомендую разрешить pandas выполнять такие преобразования. Вам не нужно пытаться понижать до меньшего или повышать до большего размера байта, если вы действительно не знаете, зачем это нужно.
Теперь мы можем использовать функцию apply
, чтобы применить ее ко всем значениям в столбце 2016
.
df['2016'].apply(convert_currency)
0 125000.0 1 920000.0 2 50000.0 3 350000.0 4 15000.0 Name: 2016, dtype: float64
Успех! Все значения отображаются как float64
, поэтому мы можем выполнять необходимые математические функции.
Я уверен, что более опытные читатели спрашивают, почему я просто не использовал лямбда-функцию?
Прежде чем я отвечу, вот что мы могли бы сделать в одной строке с помощью лямбда-функции:
df['2016'].apply(lambda x: x.replace('$', '').replace(',', '')).astype('float')
0 125000.0 1 920000.0 2 50000.0 3 350000.0 4 15000.0 Name: 2016, dtype: float64
Используя lambda
, мы можем упростить код до одной строки, что является совершенно правильным подходом. Этот подход вызывает у меня три основных опасения:
read_csv()
. Я расскажу об этом в конце Блокнота.Некоторые читатели могут возразить, что подходы на основе lambda
имеют более высокую производительность по сравнению с пользовательской функцией. Это может быть правдой, но я считаю, что для обучения новых пользователей предпочтительнее использовать функциональный подход.
Вот полный пример преобразования данных в обоих столбцах продаж с помощью функции convert_currency
.
df['2016'] = df['2016'].apply(convert_currency)
df['2017'] = df['2017'].apply(convert_currency)
df.dtypes
Customer Number int64 Customer Name object 2016 float64 2017 float64 Percent Growth object Jan Units object Month int64 Day int64 Year int64 Active object dtype: object
В качестве другого примера использования lambda
против функции мы можем взглянуть на процесс исправления столбца Percent Growth
.
Используя lambda
:
df['Percent Growth'].apply(lambda x: x.replace('%', '')).astype('float') / 100
0 0.30 1 0.10 2 0.25 3 0.04 4 -0.15 Name: Percent Growth, dtype: float64
То же самое и с пользовательской функцией:
def convert_percent(val):
"""
Преобразование процентной строки в фактический процент с плавающей точкой:
- Удаляет %
- Делит на 100, чтобы получить десятичную дробь
"""
new_val = val.replace('%', '')
return float(new_val) / 100
df['Percent Growth'].apply(convert_percent)
0 0.30 1 0.10 2 0.25 3 0.04 4 -0.15 Name: Percent Growth, dtype: float64
Последняя настраиваемая функция, о которой я расскажу, использует np.where()
для преобразования столбца Active
в логическое значение.
Основная идея состоит в том, чтобы использовать функцию np.where()
для преобразования всех значений Y
в True
, а всему остальному назначить False
.
df["Active"] = np.where(df["Active"] == "Y", True, False)
В результате получается следующий кадр данных:
df
Customer Number | Customer Name | 2016 | 2017 | Percent Growth | Jan Units | Month | Day | Year | Active | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 10002 | Quest Industries | 125000.0 | 162500.0 | 30.00% | 500 | 1 | 10 | 2015 | True |
1 | 552278 | Smith Plumbing | 920000.0 | 1012000.0 | 10.00% | 700 | 6 | 15 | 2014 | True |
2 | 23477 | ACME Industrial | 50000.0 | 62500.0 | 25.00% | 125 | 3 | 29 | 2016 | True |
3 | 24900 | Brekke LTD | 350000.0 | 490000.0 | 4.00% | 75 | 10 | 27 | 2015 | True |
4 | 651029 | Harbor Co | 15000.0 | 12750.0 | -15.00% | Closed | 2 | 2 | 2014 | False |
Для dtype
правильно установлено значение bool
.
df.dtypes
Customer Number int64 Customer Name object 2016 float64 2017 float64 Percent Growth object Jan Units object Month int64 Day int64 Year int64 Active bool dtype: object
Независимо от того, решите ли вы использовать лямбда-функцию или другой подход, например np.where()
, все эти способы очень гибкие и могут быть настроены для ваших собственных уникальных потребностей в данных.
У pandas есть золотая середина между простой функцией astype()
и более сложными пользовательскими функциями. Эти вспомогательные функции могут быть очень полезны для преобразования определенных типов данных.
Если вы следовали инструкциям, вы заметите, что я ничего не делал с столбцами даты или столбцом Jan Units
. Оба столбца могут быть преобразованы с помощью встроенных в pandas функций, таких как pd.to_numeric()
и pd.to_datetime()
.
Причина, по которой преобразование Jan Units
проблематично, заключается в том, что в столбце содержится нечисловое значение. Если бы мы попытались использовать astype()
, то получили бы ошибку (как описано ранее). Функция pd.to_numeric()
может обрабатывать эти значения более изящно:
pd.to_numeric(df['Jan Units'], errors='coerce')
0 500.0 1 700.0 2 125.0 3 75.0 4 NaN Name: Jan Units, dtype: float64
Следует отметить несколько моментов. Во-первых, функция легко обрабатывает данные и создает столбец float64
. Кроме того, она заменяет недопустимое значение Closed
на значение NaN
, потому что мы передали аргумент errors=coerce
. Мы можем оставить это значение там или заполнить его 0
с помощью fillna(0)
:
pd.to_numeric(df['Jan Units'], errors='coerce').fillna(0)
0 500.0 1 700.0 2 125.0 3 75.0 4 0.0 Name: Jan Units, dtype: float64
Последнее преобразование, о котором я расскажу, - это преобразование отдельных столбцов месяца, дня и года в тип datetime
. Функцию pd.to_datetime()
можно настраивать, но по умолчанию она также довольно умна.
pd.to_datetime(df[['Month', 'Day', 'Year']])
0 2015-01-10 1 2014-06-15 2 2016-03-29 3 2015-10-27 4 2014-02-02 dtype: datetime64[ns]
В этом случае функция объединяет столбцы в новую серию, соответствующую типу datateime64
.
Мы должны убедиться, что присвоили эти значения обратно кадру данных:
df["Start_Date"] = pd.to_datetime(df[['Month', 'Day', 'Year']])
df["Jan Units"] = pd.to_numeric(df['Jan Units'], errors='coerce').fillna(0)
df
Customer Number | Customer Name | 2016 | 2017 | Percent Growth | Jan Units | Month | Day | Year | Active | Start_Date | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 10002 | Quest Industries | 125000.0 | 162500.0 | 30.00% | 500.0 | 1 | 10 | 2015 | True | 2015-01-10 |
1 | 552278 | Smith Plumbing | 920000.0 | 1012000.0 | 10.00% | 700.0 | 6 | 15 | 2014 | True | 2014-06-15 |
2 | 23477 | ACME Industrial | 50000.0 | 62500.0 | 25.00% | 125.0 | 3 | 29 | 2016 | True | 2016-03-29 |
3 | 24900 | Brekke LTD | 350000.0 | 490000.0 | 4.00% | 75.0 | 10 | 27 | 2015 | True | 2015-10-27 |
4 | 651029 | Harbor Co | 15000.0 | 12750.0 | -15.00% | 0.0 | 2 | 2 | 2014 | False | 2014-02-02 |
Теперь данные правильно преобразованы во все нужные нам типы:
df.dtypes
Customer Number int64 Customer Name object 2016 float64 2017 float64 Percent Growth object Jan Units float64 Month int64 Day int64 Year int64 Active bool Start_Date datetime64[ns] dtype: object
Кадр данных готов к анализу!
Основные концепции использования astype()
и пользовательских функций могут быть включены на очень раннем этапе процесса анализа данных. Если у вас есть файл с данными, который вы собираетесь обрабатывать повторно, и он всегда имеет один и тот же формат, вы можете задать параметры dtype
и converters
, которые будут применяться при чтении данных. Полезно думать о dtype
как о выполнении функции astype()
для данных. Аргументы converters
позволяют применять функции к различным входным столбцам аналогично подходам, описанным выше.
Важно отметить, что вы можете применить dtype
или функцию converter
к указанному столбцу только один раз, используя этот подход. Если вы попытаетесь применить оба к одному столбцу, то dtype
будет пропущен.
Вот упрощенный пример, который выполняет почти все преобразования во время считывания данных:
df_2 = pd.read_csv("https://github.com/dm-fedorov/pandas_basic/blob/master/%D0%B1%D1%8B%D1%81%D1%82%D1%80%D0%BE%D0%B5%20%D0%B2%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%B2%20pandas/data/sales_data_types.csv?raw=True",
dtype={'Customer Number': 'int'},
converters={'2016': convert_currency,
'2017': convert_currency,
'Percent Growth': convert_percent,
'Jan Units': lambda x: pd.to_numeric(x, errors='coerce'),
'Active': lambda x: np.where(x == "Y", True, False)
})
df_2
Customer Number | Customer Name | 2016 | 2017 | Percent Growth | Jan Units | Month | Day | Year | Active | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 10002 | Quest Industries | 125000.0 | 162500.0 | 0.30 | 500.0 | 1 | 10 | 2015 | True |
1 | 552278 | Smith Plumbing | 920000.0 | 1012000.0 | 0.10 | 700.0 | 6 | 15 | 2014 | True |
2 | 23477 | ACME Industrial | 50000.0 | 62500.0 | 0.25 | 125.0 | 3 | 29 | 2016 | True |
3 | 24900 | Brekke LTD | 350000.0 | 490000.0 | 0.04 | 75.0 | 10 | 27 | 2015 | True |
4 | 651029 | Harbor Co | 15000.0 | 12750.0 | -0.15 | NaN | 2 | 2 | 2014 | False |
df_2.dtypes
Customer Number int64 Customer Name object 2016 float64 2017 float64 Percent Growth float64 Jan Units float64 Month int64 Day int64 Year int64 Active object dtype: object
Как упоминалось ранее, я решил включить пример lambda
, а также пример функции для преобразования данных. Единственная функция, которую здесь нельзя применить, - это преобразование столбцов Month
, Day
и Year
в соответствующий столбец datetime
. Тем не менее, это мощное соглашение, которое может помочь улучшить конвейер обработки данных.
Один из первых шагов при изучении нового набора данных - убедиться, что типы данных установлены правильно. В большинстве случаев pandas делает разумные выводы, но в наборах данных достаточно тонкостей, поэтому важно знать, как использовать различные параметры преобразования данных, доступные в pandas.
Подписка на онлайн-обучение