В предыдущей статье (а тут перевод на русский язык) я писал о типах данных в pandas; что это такое и как преобразовать данные в соответствующий тип. В этой статье основное внимание будет уделено категориальному типу данных, а также некоторым преимуществам и недостаткам его использования.
Оригинал статьи Криса тут.
В этой статье речь пойдет о категориальных данных. Напоминаю, что категориальные данные - это данные, которые принимают конечное число возможных значений. Например, если мы говорим о таком товаре как футболка, у него могут быть следующие категориальные значения:
Размер
(X-Small, Small, Medium, Large, X-Large) Цвет
(красный, черный, белый) Стиль
(короткий рукав, длинный рукав) Материал
(хлопок, полиэстер)Такие атрибуты, как стоимость, цена, количество, обычно являются целыми числами или числами с плавающей точкой.
Является ли переменная категориальной, зависит от ее применения. Поскольку у нас всего 3 цвета рубашек, то это хорошая категориальная переменная. Однако в другом случае "цвет" может представлять тысячи значений.
Не существует жесткого правила, определяющего, сколько значений должна иметь категориальная переменная. Вы должны использовать собственные знания о предметной области, чтобы сделать выбор. В этой статье мы рассмотрим один из подходов к определению категориальных значений.
Тип данных категории (category data type
) в pandas - это гибридный тип. Во многих случаях он выглядит и ведет себя как строка, но внутренне представлен массивом целых чисел. Это позволяет сортировать данные в произвольном порядке и более эффективно их хранить.
В конце концов, почему мы так беспокоимся об использовании категориальных значений? Есть 3 основные причины:
X-Small < Small < Medium < Large < X-Large
. Алфавитная сортировка не сможет воспроизвести этот порядок.Одно из основных преимуществ категориальных типов данных - более эффективное использование памяти. Для демонстрации этого будем использовать большой набор данных из Центров услуг Медикэр и Медикэйд в США. Этот набор данных включает csv файл размером 500 МБ+, содержащий информацию о платежах за исследования врачам и больницам в 2017 финансовом году (прямая ссылка на скачивание архива).
Сначала настройте импорт и прочтите все данные:
import pandas as pd
# https://pandas.pydata.org/pandas-docs/stable/user_guide/categorical.html#categoricaldtype
from pandas.api.types import CategoricalDtype
!wget https://www.dropbox.com/s/jou3p1zdyvjmq4e/OP_DTL_RSRCH_PGYR2017_P06302020.csv
df_raw = pd.read_csv('OP_DTL_RSRCH_PGYR2017_P06302020.csv', low_memory=False)
df_raw.head()
Change_Type | Covered_Recipient_Type | Noncovered_Recipient_Entity_Name | Teaching_Hospital_CCN | Teaching_Hospital_ID | Teaching_Hospital_Name | Physician_Profile_ID | Physician_First_Name | Physician_Middle_Name | Physician_Last_Name | ... | Preclinical_Research_Indicator | Delay_in_Publication_Indicator | Name_of_Study | Dispute_Status_for_Publication | Record_ID | Program_Year | Payment_Publication_Date | ClinicalTrials_Gov_Identifier | Research_Information_Link | Context_of_Research | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | UNCHANGED | Covered Recipient Teaching Hospital | NaN | 410007.0 | 4819.0 | RHODE ISLAND HOSPITAL | NaN | NaN | NaN | NaN | ... | No | No | PALLASPALBOCICLIB COLLABORATIVE ADJUVANT STUDY... | No | 501845079 | 2017 | 06/30/2020 | NaN | NaN | NaN |
1 | UNCHANGED | Covered Recipient Teaching Hospital | NaN | 390111.0 | 5027.0 | HOSPITAL OF THE UNIV OF PENNA | NaN | NaN | NaN | NaN | ... | No | No | An Open-Label, Single-Arm, Multicenter, Phase ... | No | 506101597 | 2017 | 06/30/2020 | NaN | NaN | 10011004 C2D15 Dec 15 2016 |
2 | UNCHANGED | Covered Recipient Teaching Hospital | NaN | 10033.0 | 5681.0 | UNIVERSITY OF ALABAMA HOSPITAL | NaN | NaN | NaN | NaN | ... | No | No | PHASE 3 STUDY OF ANTI PDL1 WITH ABRAXANE IN TN... | No | 485544131 | 2017 | 06/30/2020 | NaN | NaN | NaN |
3 | UNCHANGED | Covered Recipient Teaching Hospital | NaN | 490007.0 | 5507.0 | SENTARA NORFOLK GENERAL HOSPITAL | NaN | NaN | NaN | NaN | ... | No | No | QP ExCELs | No | 509865461 | 2017 | 06/30/2020 | NaN | NaN | NaN |
4 | UNCHANGED | Covered Recipient Teaching Hospital | NaN | 520078.0 | 5350.0 | ST. FRANCIS HOSPITAL | NaN | NaN | NaN | NaN | ... | No | No | Dimethyl Fumarate (DMF) Observational Study | No | 455803127 | 2017 | 06/30/2020 | NaN | NaN | NaN |
5 rows × 176 columns
Я установил параметр low_memory=False
, как указано в предупрждении:
interactiveshell.py:2728: DtypeWarning: Columns (..) have mixed types. Specify dtype option on import or set low_memory=False.
interactivity=interactivity, compiler=compiler, result=result)
Не стесняйтесь прочитать об этом параметре в документации по
read_csv
.
В этом наборе данных есть одна интересная особенность: в нем более 176 столбцов, но многие из них пусты. Я нашел решение на stack overflow, позволяющее быстро удалить столбцы, в которых не менее 90% данных отсутствуют.
Думаю, что это решение может быть полезно и для других:
drop_thresh = df_raw.shape[0]*.9
df = df_raw.dropna(thresh=drop_thresh, how='all', axis='columns').copy()
Давайте посмотрим на размер различных кадров данных. Вот исходный набор данных:
df_raw.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 673227 entries, 0 to 673226 Columns: 176 entries, Change_Type to Context_of_Research dtypes: float64(34), int64(3), object(139) memory usage: 904.0+ MB
CSV-файл размером 560 МБ
занимает в памяти около 904 МБ
. Кажется, что это много, но даже в слабом ноутбуке есть несколько гигабайт оперативной памяти, поэтому нам не понадобятся специализированные инструменты обработки.
Вот набор данных, который мы будем использовать в оставшейся части Блокнота:
df.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 673227 entries, 0 to 673226 Data columns (total 34 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Change_Type 673227 non-null object 1 Covered_Recipient_Type 673227 non-null object 2 Recipient_Primary_Business_Street_Address_Line1 672568 non-null object 3 Recipient_City 672568 non-null object 4 Recipient_State 672008 non-null object 5 Recipient_Zip_Code 672008 non-null object 6 Recipient_Country 672568 non-null object 7 Principal_Investigator_1_Profile_ID 636770 non-null float64 8 Principal_Investigator_1_First_Name 636770 non-null object 9 Principal_Investigator_1_Last_Name 636770 non-null object 10 Principal_Investigator_1_Business_Street_Address_Line1 636770 non-null object 11 Principal_Investigator_1_City 636770 non-null object 12 Principal_Investigator_1_State 636749 non-null object 13 Principal_Investigator_1_Zip_Code 636749 non-null object 14 Principal_Investigator_1_Country 636770 non-null object 15 Principal_Investigator_1_Primary_Type 636770 non-null object 16 Principal_Investigator_1_Specialty 635907 non-null object 17 Principal_Investigator_1_License_State_code1 636770 non-null object 18 Submitting_Applicable_Manufacturer_or_Applicable_GPO_Name 673227 non-null object 19 Applicable_Manufacturer_or_Applicable_GPO_Making_Payment_ID 673227 non-null int64 20 Applicable_Manufacturer_or_Applicable_GPO_Making_Payment_Name 673227 non-null object 21 Applicable_Manufacturer_or_Applicable_GPO_Making_Payment_State 608591 non-null object 22 Applicable_Manufacturer_or_Applicable_GPO_Making_Payment_Country 673227 non-null object 23 Related_Product_Indicator 673227 non-null object 24 Total_Amount_of_Payment_USDollars 673227 non-null float64 25 Date_of_Payment 673227 non-null object 26 Form_of_Payment_or_Transfer_of_Value 673227 non-null object 27 Preclinical_Research_Indicator 673227 non-null object 28 Delay_in_Publication_Indicator 673227 non-null object 29 Name_of_Study 666425 non-null object 30 Dispute_Status_for_Publication 673227 non-null object 31 Record_ID 673227 non-null int64 32 Program_Year 673227 non-null int64 33 Payment_Publication_Date 673227 non-null object dtypes: float64(2), int64(3), object(29) memory usage: 174.6+ MB
Теперь, когда у нас есть 33 столбца, занимающих 174,6 МБ
памяти, давайте посмотрим, какие столбцы могут стать хорошими кандидатами для категориального типа данных.
Чтобы упростить задачу, я создал небольшую вспомогательную функцию для формирования кадра данных, показывающего все уникальные значения в столбце.
# from_records: создает объект DataFrame из структурированного массива
unique_counts = pd.DataFrame.from_records([(col, df[col].nunique()) for col in df.columns],
columns=['Column_Name', 'Num_Unique']).sort_values(by=['Num_Unique'])
unique_counts
Column_Name | Num_Unique | |
---|---|---|
33 | Payment_Publication_Date | 1 |
28 | Delay_in_Publication_Indicator | 1 |
32 | Program_Year | 1 |
30 | Dispute_Status_for_Publication | 2 |
27 | Preclinical_Research_Indicator | 2 |
23 | Related_Product_Indicator | 2 |
26 | Form_of_Payment_or_Transfer_of_Value | 3 |
14 | Principal_Investigator_1_Country | 4 |
0 | Change_Type | 4 |
1 | Covered_Recipient_Type | 4 |
15 | Principal_Investigator_1_Primary_Type | 6 |
6 | Recipient_Country | 9 |
22 | Applicable_Manufacturer_or_Applicable_GPO_Maki... | 21 |
21 | Applicable_Manufacturer_or_Applicable_GPO_Maki... | 35 |
4 | Recipient_State | 54 |
17 | Principal_Investigator_1_License_State_code1 | 54 |
12 | Principal_Investigator_1_State | 55 |
16 | Principal_Investigator_1_Specialty | 244 |
25 | Date_of_Payment | 365 |
18 | Submitting_Applicable_Manufacturer_or_Applicab... | 569 |
19 | Applicable_Manufacturer_or_Applicable_GPO_Maki... | 637 |
20 | Applicable_Manufacturer_or_Applicable_GPO_Maki... | 649 |
11 | Principal_Investigator_1_City | 4209 |
3 | Recipient_City | 4454 |
8 | Principal_Investigator_1_First_Name | 8639 |
5 | Recipient_Zip_Code | 13901 |
13 | Principal_Investigator_1_Zip_Code | 14406 |
29 | Name_of_Study | 14460 |
9 | Principal_Investigator_1_Last_Name | 22355 |
10 | Principal_Investigator_1_Business_Street_Addre... | 30726 |
7 | Principal_Investigator_1_Profile_ID | 31221 |
2 | Recipient_Primary_Business_Street_Address_Line1 | 41664 |
24 | Total_Amount_of_Payment_USDollars | 156164 |
31 | Record_ID | 673227 |
Эта таблица указывает на несколько моментов, которые помогают определить категориальные значения. Во-первых, когда мы превышаем 649
уникальных значений, то происходит резкий скачок. Это может стать полезным пределом для данного набора.
Кроме того, поля с датами не следует преобразовывать в категориальные.
Самый простой способ преобразовать столбец в категориальный тип - использовать astype('category')
.
Мы можем использовать цикл для преобразования всех столбцов, которые нам нужны, используя astype('category')
:
cols_to_exclude = ['Program_Year', 'Date_of_Payment', 'Payment_Publication_Date']
for col in df.columns:
if df[col].nunique() < 700 and col not in cols_to_exclude:
df[col] = df[col].astype('category')
Если мы вызовем df.info()
для просмотра используемой памяти, то увидим уменьшение кадра данных с 175 МБ
до 92 МБ
:
df.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 673227 entries, 0 to 673226 Data columns (total 34 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Change_Type 673227 non-null category 1 Covered_Recipient_Type 673227 non-null category 2 Recipient_Primary_Business_Street_Address_Line1 672568 non-null object 3 Recipient_City 672568 non-null object 4 Recipient_State 672008 non-null category 5 Recipient_Zip_Code 672008 non-null object 6 Recipient_Country 672568 non-null category 7 Principal_Investigator_1_Profile_ID 636770 non-null float64 8 Principal_Investigator_1_First_Name 636770 non-null object 9 Principal_Investigator_1_Last_Name 636770 non-null object 10 Principal_Investigator_1_Business_Street_Address_Line1 636770 non-null object 11 Principal_Investigator_1_City 636770 non-null object 12 Principal_Investigator_1_State 636749 non-null category 13 Principal_Investigator_1_Zip_Code 636749 non-null object 14 Principal_Investigator_1_Country 636770 non-null category 15 Principal_Investigator_1_Primary_Type 636770 non-null category 16 Principal_Investigator_1_Specialty 635907 non-null category 17 Principal_Investigator_1_License_State_code1 636770 non-null category 18 Submitting_Applicable_Manufacturer_or_Applicable_GPO_Name 673227 non-null category 19 Applicable_Manufacturer_or_Applicable_GPO_Making_Payment_ID 673227 non-null category 20 Applicable_Manufacturer_or_Applicable_GPO_Making_Payment_Name 673227 non-null category 21 Applicable_Manufacturer_or_Applicable_GPO_Making_Payment_State 608591 non-null category 22 Applicable_Manufacturer_or_Applicable_GPO_Making_Payment_Country 673227 non-null category 23 Related_Product_Indicator 673227 non-null category 24 Total_Amount_of_Payment_USDollars 673227 non-null float64 25 Date_of_Payment 673227 non-null object 26 Form_of_Payment_or_Transfer_of_Value 673227 non-null category 27 Preclinical_Research_Indicator 673227 non-null category 28 Delay_in_Publication_Indicator 673227 non-null category 29 Name_of_Study 666425 non-null object 30 Dispute_Status_for_Publication 673227 non-null category 31 Record_ID 673227 non-null int64 32 Program_Year 673227 non-null int64 33 Payment_Publication_Date 673227 non-null object dtypes: category(19), float64(2), int64(2), object(11) memory usage: 91.9+ MB
Это впечатляет! Мы сократили использование памяти почти вдвое, просто преобразовав большинство столбцов в категориальные значения.
Есть еще одна функция, которую можно использовать с категориальными данными - определение пользовательского порядка.
Чтобы проиллюстрировать это, давайте сделаем краткую сводку общей суммы платежей, произведенных с использованием одного из способов оплаты:
# to_frame(): преобразует Series в DataFrame
df.groupby('Covered_Recipient_Type')['Total_Amount_of_Payment_USDollars'].sum().to_frame()
Total_Amount_of_Payment_USDollars | |
---|---|
Covered_Recipient_Type | |
Covered Recipient Physician | 1.037440e+08 |
Covered Recipient Teaching Hospital | 1.140148e+09 |
Non-covered Recipient Entity | 4.009361e+09 |
Non-covered Recipient Individual | 3.200450e+06 |
Если мы хотим изменить порядок Covered_Recipient_Type
, то нам нужно определить настраиваемый CategoricalDtype
:
# расположение в списке задает будущий порядок сортировки категорий от меньшей к большей
cats_to_order = ["Non-covered Recipient Entity",
"Covered Recipient Teaching Hospital",
"Covered Recipient Physician",
"Non-covered Recipient Individual"]
covered_type = CategoricalDtype(categories=cats_to_order,
ordered=True) # учитывать порядок категорий
covered_type
CategoricalDtype(categories=['Non-covered Recipient Entity', 'Covered Recipient Teaching Hospital', 'Covered Recipient Physician', 'Non-covered Recipient Individual'], ordered=True)
Затем явно измените порядок категории в столбце:
# https://pandas.pydata.org/docs/reference/api/pandas.Series.cat.reorder_categories.html
df['Covered_Recipient_Type'] = df['Covered_Recipient_Type'].cat.reorder_categories(cats_to_order, ordered=True)
df['Covered_Recipient_Type'][:3]
0 Covered Recipient Teaching Hospital 1 Covered Recipient Teaching Hospital 2 Covered Recipient Teaching Hospital Name: Covered_Recipient_Type, dtype: category Categories (4, object): ['Non-covered Recipient Entity' < 'Covered Recipient Teaching Hospital' < 'Covered Recipient Physician' < 'Non-covered Recipient Individual']
Теперь можем увидеть порядок сортировки в groupby
:
df.groupby('Covered_Recipient_Type')['Total_Amount_of_Payment_USDollars'].sum().to_frame()
Total_Amount_of_Payment_USDollars | |
---|---|
Covered_Recipient_Type | |
Non-covered Recipient Entity | 4.009361e+09 |
Covered Recipient Teaching Hospital | 1.140148e+09 |
Covered Recipient Physician | 1.037440e+08 |
Non-covered Recipient Individual | 3.200450e+06 |
Можете указать это преобразование при чтении CSV файла, передав словарь имен и типов столбцов через параметр dtype
:
df_raw_2 = pd.read_csv('OP_DTL_RSRCH_PGYR2017_P06302020.csv',
dtype={'Covered_Recipient_Type':covered_type},
low_memory=False)
Мы показали, что размер кадра данных уменьшается за счет преобразования значений в категориальные типы данных. Влияет ли это на другие сферы деятельности? Ответ положительный.
Вот пример операции groupby
над категориальными (categorical
) типами данных против типа данных object
.
Сначала выполните анализ исходного кадра данных:
%%timeit
df_raw.groupby('Covered_Recipient_Type')['Total_Amount_of_Payment_USDollars'].sum().to_frame()
70.5 ms ± 6.12 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
Далее кадр данных с категориальными типами:
%%timeit
df.groupby('Covered_Recipient_Type')['Total_Amount_of_Payment_USDollars'].sum().to_frame()
3.97 ms ± 60 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Мы ускорили код в 10 раз с 55,3 мс
до 4,17 мс
. Вы можете себе представить, что на гораздо больших наборах данных ускорение может быть еще большим!
Категориальные данные кажутся довольно изящными. Это экономит память и ускоряет код, так почему бы не использовать их везде? Что ж, Дональд Кнут прав, когда предупреждает о преждевременной оптимизации (рекомендую сайт на русском языке):
Программисты тратят огромное количество времени, размышляя и беспокоясь о некритичных местах кода, и пытаются оптимизировать их, что исключительно негативно сказывается на последующей отладке и поддержке. Мы должны вообще забыть об оптимизации в, скажем, 97% случаев. Поспешная оптимизация является корнем всех зол. И, напротив, мы должны уделить все внимание оставшимся 3%.
В приведенных выше примерах код работает быстрее, но это не имеет значения, когда он используется для быстрых сводных действий, которые выполняются нечасто. Кроме того, вся работа по вычислению и преобразованию в категориальные данные, вероятно, не стоит затраченных усилий для этого набора данных и простого анализа.
Также категориальные данные могут привести к неожиданным результатам при использовании в реальном мире. Приведенные ниже примеры проиллюстрируют несколько проблем.
Давайте создадим простой кадр данных с одной упорядоченной категориальной переменной, которая представляет статус клиента. Этот тривиальный пример выделит некоторые потенциальные тонкие ошибки при работе с категориальными переменными.
Стоит отметить, что в примере показано, как использовать astype()
для преобразования в упорядоченную категорию за один шаг вместо двухэтапного процесса, который использовался ранее.
import pandas as pd
from pandas.api.types import CategoricalDtype
sales_1 = [{'account': 'Jones LLC', 'Status': 'Gold', 'Jan': 150, 'Feb': 200, 'Mar': 140},
{'account': 'Alpha Co', 'Status': 'Gold', 'Jan': 200, 'Feb': 210, 'Mar': 215},
{'account': 'Blue Inc', 'Status': 'Silver', 'Jan': 50, 'Feb': 90, 'Mar': 95 }]
df_1 = pd.DataFrame(sales_1)
df_1
account | Status | Jan | Feb | Mar | |
---|---|---|---|---|---|
0 | Jones LLC | Gold | 150 | 200 | 140 |
1 | Alpha Co | Gold | 200 | 210 | 215 |
2 | Blue Inc | Silver | 50 | 90 | 95 |
status_type = CategoricalDtype(categories=['Silver', 'Gold'],
ordered=True)
df_1['Status'] = df_1['Status'].astype(status_type)
В результате получается простой кадр данных, который выглядит так:
df_1
account | Status | Jan | Feb | Mar | |
---|---|---|---|---|---|
0 | Jones LLC | Gold | 150 | 200 | 140 |
1 | Alpha Co | Gold | 200 | 210 | 215 |
2 | Blue Inc | Silver | 50 | 90 | 95 |
Можем рассмотреть категориальный столбец более подробно:
df_1['Status']
0 Gold 1 Gold 2 Silver Name: Status, dtype: category Categories (2, object): ['Silver' < 'Gold']
Все выглядит хорошо.
Мы видим, что все данные присутствуют и Gold > Silver
.
Теперь давайте добавим еще один кадр данных и применим ту же категорию к столбцу статуса:
sales_2 = [{'account': 'Smith Co', 'Status': 'Silver', 'Jan': 100, 'Feb': 100, 'Mar': 70},
{'account': 'Bingo', 'Status': 'Bronze', 'Jan': 310, 'Feb': 65, 'Mar': 80}]
df_2 = pd.DataFrame(sales_2)
df_2.head()
account | Status | Jan | Feb | Mar | |
---|---|---|---|---|---|
0 | Smith Co | Silver | 100 | 100 | 70 |
1 | Bingo | Bronze | 310 | 65 | 80 |
df_2['Status'] = df_2['Status'].astype(status_type)
df_2['Status']
0 Silver 1 NaN Name: Status, dtype: category Categories (2, object): ['Silver' < 'Gold']
df_2
account | Status | Jan | Feb | Mar | |
---|---|---|---|---|---|
0 | Smith Co | Silver | 100 | 100 | 70 |
1 | Bingo | NaN | 310 | 65 | 80 |
Хм. Что-то случилось с нашим статусом.
Посмотрим на столбец:
df_2['Status']
0 Silver 1 NaN Name: Status, dtype: category Categories (2, object): ['Silver' < 'Gold']
Поскольку мы не определили Bronze
как действующий статус, то получаем значение NaN
. Pandas делает это по вполне уважительной причине. Предполагается, что вы заранее определили все допустимые категории.
Можно только представить, насколько запутанной могла бы стать эта проблема, если бы вы ее сразу не нашли.
Этот сценарий относительно легко обнаружить, но что бы вы сделали, если бы было 100 значений, а данные не были очищены и нормализованы?
Вот еще один хитрый пример, когда вы можете "потерять" категориальный тип данных:
sales_1 = [{'account': 'Jones LLC', 'Status': 'Gold', 'Jan': 150, 'Feb': 200, 'Mar': 140},
{'account': 'Alpha Co', 'Status': 'Gold', 'Jan': 200, 'Feb': 210, 'Mar': 215},
{'account': 'Blue Inc', 'Status': 'Silver', 'Jan': 50, 'Feb': 90, 'Mar': 95 }]
df_1 = pd.DataFrame(sales_1)
df_1
account | Status | Jan | Feb | Mar | |
---|---|---|---|---|---|
0 | Jones LLC | Gold | 150 | 200 | 140 |
1 | Alpha Co | Gold | 200 | 210 | 215 |
2 | Blue Inc | Silver | 50 | 90 | 95 |
# Определим неупорядоченную категорию
df_1['Status'] = df_1['Status'].astype('category')
df_1['Status']
0 Gold 1 Gold 2 Silver Name: Status, dtype: category Categories (2, object): ['Gold', 'Silver']
sales_2 = [{'account': 'Smith Co', 'Status': 'Silver', 'Jan': 100, 'Feb': 100, 'Mar': 70},
{'account': 'Bingo', 'Status': 'Bronze', 'Jan': 310, 'Feb': 65, 'Mar': 80}]
df_2 = pd.DataFrame(sales_2)
df_2
account | Status | Jan | Feb | Mar | |
---|---|---|---|---|---|
0 | Smith Co | Silver | 100 | 100 | 70 |
1 | Bingo | Bronze | 310 | 65 | 80 |
df_2['Status'] = df_2['Status'].astype('category')
df_2['Status']
0 Silver 1 Bronze Name: Status, dtype: category Categories (2, object): ['Bronze', 'Silver']
# Объединим два кадра данных в 1
df_combined = pd.concat([df_1, df_2])
df_combined
account | Status | Jan | Feb | Mar | |
---|---|---|---|---|---|
0 | Jones LLC | Gold | 150 | 200 | 140 |
1 | Alpha Co | Gold | 200 | 210 | 215 |
2 | Blue Inc | Silver | 50 | 90 | 95 |
0 | Smith Co | Silver | 100 | 100 | 70 |
1 | Bingo | Bronze | 310 | 65 | 80 |
Все выглядит нормально, но при дополнительном осмотре мы потеряли категоририальный тип данных:
df_combined['Status']
0 Gold 1 Gold 2 Silver 0 Silver 1 Bronze Name: Status, dtype: object
В этом примере все данные на месте, но тип был преобразован в object
. Опять же, это попытка pandas объединить данные без ошибок и без предположений. Если вы хотите преобразовать данные в тип категории, то можете использовать astype('category')
.
Теперь, когда вы знаете об этих подводных камнях, то можете их отслеживать. Я дам несколько рекомендаций, как использовать категориальные типы данных:
category
) pandas.NaN
после объединения или преобразования кадров данных.Надеюсь, эта статья была полезной. Категориальные типы данных в pandas могут быть очень полезны. Однако есть несколько проблем, на которые нужно обратить внимание, чтобы не запутаться в последующей обработке.
Подписка на онлайн-обучение