Как открепить df из таблицы Excel с несколькими уровнями дублирующихся столбцов? Установить мультииндекс?

#python #pandas #dataframe #indexing #multi-index

#python #pandas #dataframe #индексирование #мультииндекс

Вопрос:

df, прочитанный из xlsx: df = pd.read_excel('file.xlsx') , поступает следующим образом:

    Age Male Female Male.1 Female.1
0  NaN  Big  Small  Small      Big
1  1.0    2      3      2        3
2  2.0    3      4      3        4
3  3.0    4      5      4        5
df = pd.DataFrame({'Age':[np.nan, 1,2,3],'Male':['Big',2,3,4],'Female':['Small',3,4,5],'Male.1':['Small',2,3,4],'Female.1':['Big',3,4,5]})
  

Обратите внимание, что Pandas добавила повторяющиеся столбцы с суффиксом .1 , что было нежелательно. Я хотел бы открепить / расплавить, чтобы получить это или подобное:

     Age Gender  Size    [measure]
1   1   Male    Big     2
2   2   Male    Big     3
3   3   Male    Big     4
4   1   Female  Big     3
5   2   Female  Big     4
6   3   Female  Big     5
7   1   Male    Small   2
8   2   Male    Small   3
9   3   Male    Small   4
10  1   Female  Small   3
11  2   Female  Small   4
12  3   Female  Small   5
  

Переименование столбцов и распаковка приближаются, но без сигары:

 df= df.rename(columns={'Male.1': 'Male', 'Female.1':'Female'})
df= df.set_index(['Age']).unstack()
  

Как я могу установить, что 1-я строка является 2-м уровнем индекса столбцов, как показано здесь? Чего мне не хватает?

Ответ №1:

Вместо .unstack() другого подхода было бы .melt() .

Вы можете транспонировать фрейм данных с помощью .T и использовать все после первой строки с помощью .iloc[1:] . Затем, .rename столбцы, .replace .1 с некоторым регулярным выражением, .melt фрейм данных и .sort_values .

 df = pd.DataFrame({'Age':[np.nan, 1,2,3],'Male':['Big',2,3,4],'Female':['Small',3,4,5],'Male.1':['Small',2,3,4],'Female.1':['Big',3,4,5]})
df = (df.T.reset_index().iloc[1:]
      .rename({'index' : 'Gender', 0 : 'Size'}, axis=1)
      .replace(r'.d $', '', regex=True)
      .melt(id_vars=['Gender', 'Size'], value_name='[measure]', var_name='Age')
      .sort_values(['Size', 'Gender', 'Age'], ascending=[True,False,True])
      .reset_index(drop=True))
df = df[['Age', 'Gender', 'Size', '[measure]']]      
df
Out[41]: 
   Age  Gender   Size  [measure]
0    1    Male    Big          2
1    2    Male    Big          3
2    3    Male    Big          4
3    1  Female    Big          3
4    2  Female    Big          4
5    3  Female    Big          5
6    1    Male  Small          2
7    2    Male  Small          3
8    3    Male  Small          4
9    1  Female  Small          3
10   2  Female  Small          4
11   3  Female  Small          5
  

Ответ №2:

Если возможно, создайте с первыми 2 строками MultiIndex , а также с первым столбцом для индексации по header и index_col параметру в read_excel :

 df = pd.read_excel('file.xlsx',header=[0,1], index_col=[0])
    
print (df)
Age Male Female  Male Female
     Big  Small Small    Big
1.0    2      3     2      3
2.0    3      4     3      4
3.0    4      5     4      5

print (df.columns)
MultiIndex([(  'Male',   'Big'),
            ('Female', 'Small'),
            (  'Male', 'Small'),
            ('Female',   'Big')],
           names=['Age', None])

print (df.index)
Float64Index([1.0, 2.0, 3.0], dtype='float64')
  

Итак, возможно ли использование DataFrame.unstack :

 df = (df.unstack()
        .rename_axis(['Gender', 'Size','Age'])
        .reset_index(name='measure'))
print (df)
    Gender   Size  Age  measure
0     Male    Big  1.0        2
1     Male    Big  2.0        3
2     Male    Big  3.0        4
3   Female  Small  1.0        3
4   Female  Small  2.0        4
5   Female  Small  3.0        5
6     Male  Small  1.0        2
7     Male  Small  2.0        3
8     Male  Small  3.0        4
9   Female    Big  1.0        3
10  Female    Big  2.0        4
11  Female    Big  3.0        5
  

Если это невозможно, используйте:

Вы можете создать MultiIndex by MultiIndex.from_arrays и удалить last . с помощью digit by replace , затем отфильтровать первую строку по DataFrame.iloc и изменить форму по DataFrame.melt по первому столбцу, последним задать имена новых столбцов:

 df.columns = pd.MultiIndex.from_arrays([df.columns.str.replace(r'.d $', ''), 
                                        df.iloc[0]])
df = df.iloc[1:].melt(df.columns[:1].tolist())
df.columns=['Age','Gender','Size','measure']
print (df)
    Age  Gender   Size measure
0   1.0    Male    Big       2
1   2.0    Male    Big       3
2   3.0    Male    Big       4
3   1.0  Female  Small       3
4   2.0  Female  Small       4
5   3.0  Female  Small       5
6   1.0    Male  Small       2
7   2.0    Male  Small       3
8   3.0    Male  Small       4
9   1.0  Female    Big       3
10  2.0  Female    Big       4
11  3.0  Female    Big       5
  

Возможно решение с DataFrame.unstack , только установите для первого столбца значение index by DataFrame.set_index и уровни MultiIndex by Series.rename_axis для имен новых столбцов:

 df.columns = pd.MultiIndex.from_arrays([df.columns.str.replace(r'.d $', ''), 
                                        df.iloc[0]])
df = (df.iloc[1:].set_index(df.columns[:1].tolist())
        .unstack()
        .rename_axis(['Gender', 'Size','Age'])
        .reset_index(name='measure'))
print (df)
    Gender   Size  Age measure
0     Male    Big  1.0       2
1     Male    Big  2.0       3
2     Male    Big  3.0       4
3   Female  Small  1.0       3
4   Female  Small  2.0       4
5   Female  Small  3.0       5
6     Male  Small  1.0       2
7     Male  Small  2.0       3
8     Male  Small  3.0       4
9   Female    Big  1.0       3
10  Female    Big  2.0       4
11  Female    Big  3.0       5
  

Ответ №3:

Создайте столбец с несколькими индексами, объединив строку 0 со столбцом :

 df.columns = pd.MultiIndex.from_arrays((df.columns, df.iloc[0]))
df.columns.names = ['gender', 'size']

df.columns

MultiIndex([(     'Age',     nan),
            (    'Male',   'Big'),
            (  'Female', 'Small'),
            (  'Male.1', 'Small'),
            ('Female.1',   'Big')],
          names=['gender', 'size'])
  

Теперь вы можете изменять форму и переименовывать :

  (df
  .dropna()
  .melt([('Age', np.NaN)], value_name='measure')
  .replace(r'.d $', '', regex=True)
  .rename(columns={("Age", np.NaN) : "Age"}))

   Age  gender  size measure
0   1.0 Male    Big     2
1   2.0 Male    Big     3
2   3.0 Male    Big     4
3   1.0 Female  Small   3
4   2.0 Female  Small   4
5   3.0 Female  Small   5
6   1.0 Male    Small   2
7   2.0 Male    Small   3
8   3.0 Male    Small   4
9   1.0 Female  Big     3
10  2.0 Female  Big     4
11  3.0 Female  Big     5