#python #pandas #dataframe
#python #pandas #фрейм данных
Вопрос:
Я пытаюсь использовать файл Excel, чтобы сделать что-то, что было собрано в довольно раздражающем формате (я его не создавал; это существующий ресурс, который я использую). Интересующие значения находятся в столбце с именем (что-то вроде) All_Values
, разделенном точками, в то время как меры, соответствующие этим значениям, указаны в отдельном столбце All_Measures
, также разделены точками и различны для каждой строки. Например, используя игрушечный набор данных:
Object All_Measures All_Values (additional columns that are not like this)
1 Height.Weight 20.50 ...
2 Weight.Height 65.30 ...
3 Height.Width.Depth 22.30.10 ...
То, что я хочу сделать, это переформатировать данные следующим образом, заполнив недостающие значения 0s (окончательный порядок столбцов не важен):
Object Height Weight Width Depth (additional columns)
1 20 50 0 0 ...
2 30 65 0 0 ...
3 22 0 30 10 ...
Один из способов, которым я могу это сделать, — это (очень медленно, поскольку это большой набор данных) создать новый пустой фрейм данных, а затем выполнить итерацию по каждой строке в существующей, создать новую строку фрейма данных со столбцами, указанными путем разделения All_Measures
на .
, и значениями, указанными путем разделения All_Values
на .
. Затем я удаляю All_Measures
и All_Values
из строки и добавляю новый фрейм данных в его конец и добавляю его к пустому фрейму данных. Но это довольно неуклюже, и было бы неплохо, если бы был более быстрый и элегантный способ сделать это.
Поскольку здесь нет ошибки, у меня нет MWE, но вот некоторый код, который можно скопировать для создания игрушечного набора данных, подобного приведенному выше, на случай, если он пригодится.
df = pd.DataFrame(
columns = ['Object','All_Measures','All_Values','Object_Name']
[[1,'Height.Weight','20.50','First'],
[2,'Weight.Height','65.30','Second'],
[3,'Height.Width.Depth','22.30.10','Third']]
)
Ответ №1:
Используйте str.split
, explode
, и pivot_table
:
# split the "All" columns into lists
df['All_Measures'] = df['All_Measures'].str.split('.')
df['All_Values'] = df['All_Values'].str.split('.')
# explode the lists into rows
df = df.explode(['All_Measures', 'All_Values'])
# pivot the measures into columns
df.pivot_table(
index=['Object', 'Object_Name'],
columns='All_Measures',
values='All_Values',
fill_value=0)
Вывод:
All_Measures Depth Height Weight Width
Object Object_Name
1 First 0 20 50 0
2 Second 0 30 65 0
3 Third 10 22 0 30
Подробная разбивка
str.split
«Все» столбцы в списки:df['All_Measures'] = df['All_Measures'].str.split('.') df['All_Values'] = df['All_Values'].str.split('.') # Object All_Measures All_Values Object_Name # 0 1 [Height, Weight] [20, 50] First # 1 2 [Weight, Height] [65, 30] Second # 2 3 [Height, Width, Depth] [22, 30, 10] Third
explode
списки в строки:df = df.explode(['All_Measures', 'All_Values']) # Object All_Measures All_Values Object_Name # 0 1 Height 20 First # 0 1 Weight 50 First # 1 2 Weight 65 Second # 1 2 Height 30 Second # 2 3 Height 22 Third # 2 3 Width 30 Third # 2 3 Depth 10 Third
pivot_table
меры в столбцах:df.pivot_table( index=['Object', 'Object_Name'], columns='All_Measures', values='All_Values', fill_value=0) # All_Measures Depth Height Weight Width # Object Object_Name # 1 First 0 20 50 0 # 2 Second 0 30 65 0 # 3 Third 10 22 0 30
Комментарии:
1. Это здорово! Мне нужно было сделать только одну дополнительную вещь, которая заключалась в преобразовании сводной таблицы обратно в dataframe, используя
pd.DataFrame(df.to_records())
в конце. Спасибо, что избавили меня от головной боли и познакомили сpd.DataFrame.explode()
.2. Отлично. Я думаю, вы также можете сбросить индекс сводной таблицы, чтобы получить нечто подобное:
df.pivot_table(...).reset_index()
Ответ №2:
Вероятно, есть какой-то способ добиться этого без использования циклов или apply() , но я не могу об этом думать. Вот что приходит на ум:
import pandas as pd
df = pd.DataFrame(
[[1,'Height.Weight','20.50','First'],
[2,'Weight.Height','65.30','Second'],
[3,'Height.Width.Depth','22.30.10','Third']],
columns = ['Object','All_Measures','All_Values','Object_Name'],
)
def parse_combined_measure(row):
keys = row["All_Measures"].split(".")
values = row["All_Values"].split(".")
return row.append(pd.Series(dict(zip(keys, values))))
df2 = df.apply(parse_combined_measure, axis=1)
df2 = df2.fillna(0)
Ответ №3:
# Create a new DataFrame with just the values extracted from the All_Values column
In [24]: new_df = df['All_Values'].str.split('.').apply(pd.Series)
Out[24]:
0 1 2
0 20 50 NaN
1 65 30 NaN
2 22 30 10
# Figure out the names those columns should have
In [37]: df.loc[df['All_Measures'].str.count('.').idxmax(), 'All_Measures']
Out[37]: 'Height.Width.Depth'
In [38]: new_df.columns = df.loc[df['All_Measures'].str.count('.').idxmax(), 'All_Measures'].split('.')
Out[39]:
Height Width Depth
0 20 50 NaN
1 65 30 NaN
2 22 30 10
# Join the new DF with the original, except the columns we've expanded.
In [41]: df[['Object', 'Object_Name']].join(new_df)
Out[41]:
Object Object_Name Height Width Depth
0 1 First 20 50 NaN
1 2 Second 65 30 NaN
2 3 Third 22 30 10
Комментарии:
1. Разве вес второго элемента не должен быть 65, а высота второго элемента равна 65?
2. Да, мне нравится эта идея, но она не совсем работает для моего набора данных, где порядок в
All_Measures
строках не соответствует. Он также не учитывает тот факт, что первые две строки имеютWeight
and , а неWidth
orDepth
, но полный набор имен можно получить, выполнивnew_df.columns = list(set('.'.join(df['All_Measures']).split('.')))
вместо этого.