Panda - Cara meratakan indeks hierarkis dalam kolom


325

Saya memiliki bingkai data dengan indeks hierarki di sumbu 1 (kolom) (dari groupby.aggoperasi):

     USAF   WBAN  year  month  day  s_PC  s_CL  s_CD  s_CNT  tempf       
                                     sum   sum   sum    sum   amax   amin
0  702730  26451  1993      1    1     1     0    12     13  30.92  24.98
1  702730  26451  1993      1    2     0     0    13     13  32.00  24.98
2  702730  26451  1993      1    3     1    10     2     13  23.00   6.98
3  702730  26451  1993      1    4     1     0    12     13  10.04   3.92
4  702730  26451  1993      1    5     3     0    10     13  19.94  10.94

Saya ingin meratakannya, sehingga terlihat seperti ini (nama tidak kritis - saya bisa mengganti nama):

     USAF   WBAN  year  month  day  s_PC  s_CL  s_CD  s_CNT  tempf_amax  tmpf_amin   
0  702730  26451  1993      1    1     1     0    12     13  30.92          24.98
1  702730  26451  1993      1    2     0     0    13     13  32.00          24.98
2  702730  26451  1993      1    3     1    10     2     13  23.00          6.98
3  702730  26451  1993      1    4     1     0    12     13  10.04          3.92
4  702730  26451  1993      1    5     3     0    10     13  19.94          10.94

Bagaimana saya melakukan ini? (Saya sudah mencoba banyak, tetapi tidak berhasil.)

Per saran, di sini adalah kepala dalam bentuk dikt

{('USAF', ''): {0: '702730',
  1: '702730',
  2: '702730',
  3: '702730',
  4: '702730'},
 ('WBAN', ''): {0: '26451', 1: '26451', 2: '26451', 3: '26451', 4: '26451'},
 ('day', ''): {0: 1, 1: 2, 2: 3, 3: 4, 4: 5},
 ('month', ''): {0: 1, 1: 1, 2: 1, 3: 1, 4: 1},
 ('s_CD', 'sum'): {0: 12.0, 1: 13.0, 2: 2.0, 3: 12.0, 4: 10.0},
 ('s_CL', 'sum'): {0: 0.0, 1: 0.0, 2: 10.0, 3: 0.0, 4: 0.0},
 ('s_CNT', 'sum'): {0: 13.0, 1: 13.0, 2: 13.0, 3: 13.0, 4: 13.0},
 ('s_PC', 'sum'): {0: 1.0, 1: 0.0, 2: 1.0, 3: 1.0, 4: 3.0},
 ('tempf', 'amax'): {0: 30.920000000000002,
  1: 32.0,
  2: 23.0,
  3: 10.039999999999999,
  4: 19.939999999999998},
 ('tempf', 'amin'): {0: 24.98,
  1: 24.98,
  2: 6.9799999999999969,
  3: 3.9199999999999982,
  4: 10.940000000000001},
 ('year', ''): {0: 1993, 1: 1993, 2: 1993, 3: 1993, 4: 1993}}

5
dapatkah Anda menambahkan output df[:5].to_dict()sebagai contoh untuk dibaca orang lain dalam dataset Anda?
Zelazny7

Ide bagus. Apakah itu di atas karena terlalu lama untuk komentar.
Ross R

Ada saran tentang pandaspelacak masalah untuk menerapkan metode khusus untuk ini.
joelostblom

2
@ joelostblom dan itu sebenarnya telah diterapkan (panda 0.24.0 ke atas). Saya mengirim jawaban tetapi pada dasarnya sekarang Anda bisa melakukannya dat.columns = dat.columns.to_flat_index(). Fungsi panda bawaan.
onlyphantom

Jawaban:


471

Saya pikir cara termudah untuk melakukan ini adalah dengan mengatur kolom ke tingkat atas:

df.columns = df.columns.get_level_values(0)

Catatan: jika level to memiliki nama Anda juga dapat mengaksesnya dengan ini, daripada 0.

.

Jika Anda ingin menggabungkan / joinMultiIndex Anda menjadi satu Indeks (dengan asumsi Anda hanya memiliki entri string di kolom Anda ) Anda bisa:

df.columns = [' '.join(col).strip() for col in df.columns.values]

Catatan: kita harus stripspasi ketika ketika tidak ada indeks kedua.

In [11]: [' '.join(col).strip() for col in df.columns.values]
Out[11]: 
['USAF',
 'WBAN',
 'day',
 'month',
 's_CD sum',
 's_CL sum',
 's_CNT sum',
 's_PC sum',
 'tempf amax',
 'tempf amin',
 'year']

14
df.reset_index (inplace = True) dapat menjadi solusi alternatif.
Tobias

8
satu komentar kecil ... jika Anda ingin menggunakan _ untuk multilevel kolom gabungan .. Anda bisa menggunakan ini ... df.columns = ['_'. join (col) .strip () untuk col di df.columns. nilai]
ihightower

30
modifikasi kecil untuk mempertahankan garis bawah hanya untuk cols yang bergabung:['_'.join(col).rstrip('_') for col in df.columns.values]
Seiji Armstrong

Ini bekerja dengan baik, jika Anda hanya ingin menggunakan kolom kedua: df.columns = [col [1] untuk col di df.columns.values]
user3078500

1
Jika Anda ingin menggunakan, sum s_CDbukan yang s_CD sumbisa dilakukan df.columns = ['_'.join(col).rstrip('_') for col in [c[::-1] for c in df.columns.values]].
irene

82
pd.DataFrame(df.to_records()) # multiindex become columns and new index is integers only

3
Ini berfungsi, tetapi menyisakan nama kolom yang sulit diakses secara terprogram dan tidak dapat
dipertanyakan

1
Ini tidak akan berfungsi dengan versi panda terbaru. Ini bekerja dengan 0,18 tetapi tidak dengan 0,20 (terbaru sekarang)
TH22

1
@dmeu untuk melestarikan nama kolom pd.DataFrame(df.to_records(), columns=df.index.names + list(df.columns))
Teoretik

1
Ini melestarikan nama kolom sebagai tupel untuk saya, dan untuk menjaga indeks saya gunakan:pd.DataFrame(df_volume.to_records(), index=df_volume.index).drop('index', axis=1)
Jayen

54

Semua jawaban saat ini di utas ini pasti sedikit bertanggal. Pada pandasversi 0.24.0, .to_flat_index()lakukan apa yang Anda butuhkan.

Dari dokumentasi panda sendiri :

MultiIndex.to_flat_index ()

Ubah MultiIndex menjadi Indeks Tuples yang berisi nilai level.

Contoh sederhana dari dokumentasinya:

import pandas as pd
print(pd.__version__) # '0.23.4'
index = pd.MultiIndex.from_product(
        [['foo', 'bar'], ['baz', 'qux']],
        names=['a', 'b'])

print(index)
# MultiIndex(levels=[['bar', 'foo'], ['baz', 'qux']],
#           codes=[[1, 1, 0, 0], [0, 1, 0, 1]],
#           names=['a', 'b'])

Menerapkan to_flat_index():

index.to_flat_index()
# Index([('foo', 'baz'), ('foo', 'qux'), ('bar', 'baz'), ('bar', 'qux')], dtype='object')

Menggunakannya untuk mengganti pandaskolom yang ada

Contoh cara Anda menggunakannya dat, yaitu DataFrame dengan MultiIndexkolom:

dat = df.loc[:,['name','workshop_period','class_size']].groupby(['name','workshop_period']).describe()
print(dat.columns)
# MultiIndex(levels=[['class_size'], ['count', 'mean', 'std', 'min', '25%', '50%', '75%', 'max']],
#            codes=[[0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 2, 3, 4, 5, 6, 7]])

dat.columns = dat.columns.to_flat_index()
print(dat.columns)
# Index([('class_size', 'count'),  ('class_size', 'mean'),
#     ('class_size', 'std'),   ('class_size', 'min'),
#     ('class_size', '25%'),   ('class_size', '50%'),
#     ('class_size', '75%'),   ('class_size', 'max')],
#  dtype='object')

42

Jawaban Andy Hayden tentu saja merupakan cara termudah - jika Anda ingin menghindari label kolom duplikat Anda perlu sedikit mengubah

In [34]: df
Out[34]: 
     USAF   WBAN  day  month  s_CD  s_CL  s_CNT  s_PC  tempf         year
                               sum   sum    sum   sum   amax   amin      
0  702730  26451    1      1    12     0     13     1  30.92  24.98  1993
1  702730  26451    2      1    13     0     13     0  32.00  24.98  1993
2  702730  26451    3      1     2    10     13     1  23.00   6.98  1993
3  702730  26451    4      1    12     0     13     1  10.04   3.92  1993
4  702730  26451    5      1    10     0     13     3  19.94  10.94  1993


In [35]: mi = df.columns

In [36]: mi
Out[36]: 
MultiIndex
[(USAF, ), (WBAN, ), (day, ), (month, ), (s_CD, sum), (s_CL, sum), (s_CNT, sum), (s_PC, sum), (tempf, amax), (tempf, amin), (year, )]


In [37]: mi.tolist()
Out[37]: 
[('USAF', ''),
 ('WBAN', ''),
 ('day', ''),
 ('month', ''),
 ('s_CD', 'sum'),
 ('s_CL', 'sum'),
 ('s_CNT', 'sum'),
 ('s_PC', 'sum'),
 ('tempf', 'amax'),
 ('tempf', 'amin'),
 ('year', '')]

In [38]: ind = pd.Index([e[0] + e[1] for e in mi.tolist()])

In [39]: ind
Out[39]: Index([USAF, WBAN, day, month, s_CDsum, s_CLsum, s_CNTsum, s_PCsum, tempfamax, tempfamin, year], dtype=object)

In [40]: df.columns = ind




In [46]: df
Out[46]: 
     USAF   WBAN  day  month  s_CDsum  s_CLsum  s_CNTsum  s_PCsum  tempfamax  tempfamin  \
0  702730  26451    1      1       12        0        13        1      30.92      24.98   
1  702730  26451    2      1       13        0        13        0      32.00      24.98   
2  702730  26451    3      1        2       10        13        1      23.00       6.98   
3  702730  26451    4      1       12        0        13        1      10.04       3.92   
4  702730  26451    5      1       10        0        13        3      19.94      10.94   




   year  
0  1993  
1  1993  
2  1993  
3  1993  
4  1993

2
terima kasih Theodros! Ini adalah satu-satunya solusi yang benar yang menangani semua kasus!
CanCeylan

17
df.columns = ['_'.join(tup).rstrip('_') for tup in df.columns.values]

14

Dan jika Anda ingin mempertahankan informasi agregasi dari tingkat kedua multiindex Anda dapat mencoba ini:

In [1]: new_cols = [''.join(t) for t in df.columns]
Out[1]:
['USAF',
 'WBAN',
 'day',
 'month',
 's_CDsum',
 's_CLsum',
 's_CNTsum',
 's_PCsum',
 'tempfamax',
 'tempfamin',
 'year']

In [2]: df.columns = new_cols

new_colstidak ditentukan.
samthebrand

11

Cara paling pythonic untuk melakukan ini menggunakan mapfungsi.

df.columns = df.columns.map(' '.join).str.strip()

Output print(df.columns):

Index(['USAF', 'WBAN', 'day', 'month', 's_CD sum', 's_CL sum', 's_CNT sum',
       's_PC sum', 'tempf amax', 'tempf amin', 'year'],
      dtype='object')

Perbarui menggunakan Python 3.6+ dengan f string:

df.columns = [f'{f} {s}' if s != '' else f'{f}' 
              for f, s in df.columns]

print(df.columns)

Keluaran:

Index(['USAF', 'WBAN', 'day', 'month', 's_CD sum', 's_CL sum', 's_CNT sum',
       's_PC sum', 'tempf amax', 'tempf amin', 'year'],
      dtype='object')

9

Solusi termudah dan paling intuitif bagi saya adalah menggabungkan nama kolom menggunakan get_level_values . Ini mencegah nama kolom duplikat ketika Anda melakukan lebih dari satu agregasi pada kolom yang sama:

level_one = df.columns.get_level_values(0).astype(str)
level_two = df.columns.get_level_values(1).astype(str)
df.columns = level_one + level_two

Jika Anda ingin pemisah antar kolom, Anda bisa melakukan ini. Ini akan mengembalikan hal yang sama dengan komentar Seiji Armstrong pada jawaban yang diterima yang hanya mencakup garis bawah untuk kolom dengan nilai di kedua level indeks:

level_one = df.columns.get_level_values(0).astype(str)
level_two = df.columns.get_level_values(1).astype(str)
column_separator = ['_' if x != '' else '' for x in level_two]
df.columns = level_one + column_separator + level_two

Saya tahu ini melakukan hal yang sama dengan jawaban hebat Andy Hayden di atas, tapi saya pikir ini sedikit lebih intuitif dengan cara ini dan lebih mudah diingat (jadi saya tidak harus terus merujuk ke utas ini), terutama untuk pengguna panda pemula .

Metode ini juga lebih dapat dikembangkan jika Anda memiliki 3 level kolom.

level_one = df.columns.get_level_values(0).astype(str)
level_two = df.columns.get_level_values(1).astype(str)
level_three = df.columns.get_level_values(2).astype(str)
df.columns = level_one + level_two + level_three

6

Setelah membaca semua jawaban, saya datang dengan ini:

def __my_flatten_cols(self, how="_".join, reset_index=True):
    how = (lambda iter: list(iter)[-1]) if how == "last" else how
    self.columns = [how(filter(None, map(str, levels))) for levels in self.columns.values] \
                    if isinstance(self.columns, pd.MultiIndex) else self.columns
    return self.reset_index() if reset_index else self
pd.DataFrame.my_flatten_cols = __my_flatten_cols

Pemakaian:

Diberi bingkai data:

df = pd.DataFrame({"grouper": ["x","x","y","y"], "val1": [0,2,4,6], 2: [1,3,5,7]}, columns=["grouper", "val1", 2])

  grouper  val1  2
0       x     0  1
1       x     2  3
2       y     4  5
3       y     6  7
  • Metode agregasi tunggal : variabel yang dihasilkan dinamai sama dengan sumber :

    df.groupby(by="grouper").agg("min").my_flatten_cols()
    • Sama seperti df.groupby(by="grouper", as_index = Salah) atau .agg(...).reset_index ()
    • ----- before -----
                 val1  2
        grouper         
      
      ------ after -----
        grouper  val1  2
      0       x     0  1
      1       y     4  5
  • Variabel sumber tunggal, banyak agregasi : variabel yang dihasilkan dinamai menurut statistik :

    df.groupby(by="grouper").agg({"val1": [min,max]}).my_flatten_cols("last")
    • Sama seperti a = df.groupby(..).agg(..); a.columns = a.columns.droplevel(0); a.reset_index().
    • ----- before -----
                  val1    
                 min max
        grouper         
      
      ------ after -----
        grouper  min  max
      0       x    0    2
      1       y    4    6
  • Beberapa variabel, banyak agregasi : variabel yang dihasilkan bernama (varname) _ (statname) :

    df.groupby(by="grouper").agg({"val1": min, 2:[sum, "size"]}).my_flatten_cols()
    # you can combine the names in other ways too, e.g. use a different delimiter:
    #df.groupby(by="grouper").agg({"val1": min, 2:[sum, "size"]}).my_flatten_cols(" ".join)
    • Berjalan di a.columns = ["_".join(filter(None, map(str, levels))) for levels in a.columns.values]bawah tenda (karena ini agg()menghasilkan MultiIndexkolom-kolom).
    • Jika Anda tidak memiliki my_flatten_colshelper, mungkin lebih mudah untuk mengetikkan solusi yang disarankan oleh @Seigi :a.columns = ["_".join(t).rstrip("_") for t in a.columns.values] , yang bekerja sama dalam hal ini (tapi gagal jika Anda memiliki label numerik pada kolom)
    • Untuk menangani label numerik pada kolom, Anda bisa menggunakan solusi yang disarankan oleh @jxstanford dan @Nolan Conaway ( a.columns = ["_".join(tuple(map(str, t))).rstrip("_") for t in a.columns.values]), tapi saya tidak mengerti mengapa tuple()panggilan itu diperlukan, dan saya percaya rstrip()hanya diperlukan jika beberapa kolom memiliki deskriptor seperti ("colname", "")( yang dapat terjadi jika Anda reset_index()sebelum mencoba memperbaiki .columns)
    • ----- before -----
                 val1           2     
                 min       sum    size
        grouper              
      
      ------ after -----
        grouper  val1_min  2_sum  2_size
      0       x         0      4       2
      1       y         4     12       2
  • Anda ingin nama variabel yang dihasilkan secara manual: (ini ditinggalkan karena panda 0.20.0 dengan tidak ada alternatif yang memadai sebagai 0,23 )

    df.groupby(by="grouper").agg({"val1": {"sum_of_val1": "sum", "count_of_val1": "count"},
                                       2: {"sum_of_2":    "sum", "count_of_2":    "count"}}).my_flatten_cols("last")
    • Saran lain termasuk : mengatur kolom secara manual: res.columns = ['A_sum', 'B_sum', 'count']atau memasukkan.join() banyak groupbypernyataan.
    • ----- before -----
                         val1                      2         
                count_of_val1 sum_of_val1 count_of_2 sum_of_2
        grouper                                              
      
      ------ after -----
        grouper  count_of_val1  sum_of_val1  count_of_2  sum_of_2
      0       x              2            2           2         4
      1       y              2           10           2        12

Kasus yang ditangani oleh fungsi pembantu

  • nama level dapat berupa non-string, mis. Indeks panda DataFrame dengan nomor kolom, ketika nama kolom bilangan bulat , jadi kita harus mengonversi denganmap(str, ..)
  • mereka juga bisa kosong, jadi kita harus filter(None, ..)
  • untuk kolom tingkat tunggal (mis. apa pun kecuali MultiIndex), columns.valuesmengembalikan nama ( str, bukan tupel)
  • tergantung pada bagaimana Anda menggunakan .agg()Anda mungkin perlu menjaga label terbawah untuk kolom atau menggabungkan beberapa label
  • (Karena saya baru mengenal panda?) lebih sering daripada tidak, saya ingin reset_index()dapat bekerja dengan kolom kelompok-per-cara secara teratur, jadi ia melakukannya secara default

jawaban yang sangat bagus, bisakah Anda jelaskan bekerja pada '[" " .join (tuple (peta (str, t))). rstrip (" ") untuk t di a.columns.values]', terima kasih sebelumnya
Vineet

@Vineet Saya memperbarui posting saya untuk menunjukkan bahwa saya menyebutkan cuplikan yang menyarankannya memiliki efek yang mirip dengan solusi saya. Jika Anda ingin detail tentang mengapa tuple()diperlukan, Anda mungkin ingin mengomentari posting jxstanford. Jika tidak, mungkin akan membantu untuk memeriksa .columns.valuesdalam contoh yang diberikan: [('val1', 'min'), (2, 'sum'), (2, 'size')]. 1) for t in a.columns.valuesloop di atas kolom, untuk kolom kedua t == (2, 'sum'); 2) map(str, t)berlaku str()untuk setiap "level", menghasilkan ('2', 'sum'); 3) "_".join(('2','sum'))menghasilkan "2_sum",
Nickolay

5

Solusi umum yang menangani beberapa level dan tipe campuran:

df.columns = ['_'.join(tuple(map(str, t))) for t in df.columns.values]

1
Dalam hal ada kolom non-hierarkis juga:df.columns = ['_'.join(tuple(map(str, t))).rstrip('_') for t in df.columns.values]
Nolan Conaway

Terima kasih. Sedang mencari lama. Karena indeks Multilevel saya mengandung nilai integer. Ini menyelesaikan masalah saya :)
AnksG

4

Agak terlambat mungkin, tetapi jika Anda tidak khawatir tentang duplikat nama kolom:

df.columns = df.columns.tolist()

Bagi saya, ini mengubah nama kolom menjadi seperti tuple: (year, )dan(tempf, amax)
Nickolay

3

Jika Anda ingin memiliki pemisah dalam nama antar level, fungsi ini berfungsi dengan baik.

def flattenHierarchicalCol(col,sep = '_'):
    if not type(col) is tuple:
        return col
    else:
        new_col = ''
        for leveli,level in enumerate(col):
            if not level == '':
                if not leveli == 0:
                    new_col += sep
                new_col += level
        return new_col

df.columns = df.columns.map(flattenHierarchicalCol)

1
Saya suka itu. Meninggalkan kasus di mana kolom tidak hierarkis ini dapat disederhanakan banyak:df.columns = ["_".join(filter(None, c)) for c in df.columns]
Gigo

3

Mengikuti @jxstanford dan @ tvt173, saya menulis fungsi cepat yang seharusnya melakukan trik, terlepas dari nama kolom string / int:

def flatten_cols(df):
    df.columns = [
        '_'.join(tuple(map(str, t))).rstrip('_') 
        for t in df.columns.values
        ]
    return df

1

Anda juga bisa melakukan seperti di bawah ini. Pertimbangkan dfuntuk menjadi kerangka data Anda dan anggap indeks dua tingkat (seperti halnya dalam contoh Anda)

df.columns = [(df.columns[i][0])+'_'+(datadf_pos4.columns[i][1]) for i in range(len(df.columns))]

1

Saya akan berbagi cara langsung yang bekerja untuk saya.

[" ".join([str(elem) for elem in tup]) for tup in df.columns.tolist()]
#df = df.reset_index() if needed

0

Untuk meratakan MultiIndex di dalam rangkaian metode DataFrame lainnya, tentukan fungsi seperti ini:

def flatten_index(df):
  df_copy = df.copy()
  df_copy.columns = ['_'.join(col).rstrip('_') for col in df_copy.columns.values]
  return df_copy.reset_index()

Kemudian gunakan pipemetode untuk menerapkan fungsi ini dalam rantai metode DataFrame, setelah groupbydan aggtetapi sebelum metode lain dalam rantai:

my_df \
  .groupby('group') \
  .agg({'value': ['count']}) \
  .pipe(flatten_index) \
  .sort_values('value_count')

0

Rutinitas sederhana lain.

def flatten_columns(df, sep='.'):
    def _remove_empty(column_name):
        return tuple(element for element in column_name if element)
    def _join(column_name):
        return sep.join(column_name)

    new_columns = [_join(_remove_empty(column)) for column in df.columns.values]
    df.columns = new_columns
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.