Как разделить данные по разделителю для соответствующих столбцов name и value

#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
 

Подробная разбивка

  1. 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
     
  2. 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
     
  3. 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 or Depth , но полный набор имен можно получить, выполнив new_df.columns = list(set('.'.join(df['All_Measures']).split('.'))) вместо этого.