Pandalar - Sütunlardaki hiyerarşik bir dizin nasıl düzleştirilir


325

Eksen 1 (sütunlar) (bir groupby.aggişlemden) hiyerarşik bir dizin ile bir veri çerçevesi var :

     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

Düzleştirmek istiyorum, öyle görünüyor ki (isimler kritik değildir - yeniden adlandırabilirim):

     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

Bunu nasıl yaparım? (Çok denedim, boşuna.)

Bir öneri başına, burada dikte şeklinde kafa

{('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
çıktısını df[:5].to_dict()başkalarının veri kümenizde okuması için örnek olarak ekleyebilir misiniz ?
13:18

İyi bir fikir. Yorum için çok uzun olduğu için yukarıda yaptı.
Ross R

Sorun izleyicide bunun için özel bir yöntem uygulamak için bir öneri varpandas .
joelostblom

2
@joelostblom ve aslında uygulanmıştır (pandalar 0.24.0 ve üstü). Bir cevap gönderdim ama aslında şimdi yapabilirsiniz dat.columns = dat.columns.to_flat_index(). Dahili panda işlevi.
onlyphantom

Yanıtlar:


471

Bunu yapmanın en kolay yolunun sütunları en üst düzeye ayarlamak olacağını düşünüyorum:

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

Not: to seviyesinin bir adı varsa, 0 yerine bu adla da erişebilirsiniz.

.

/ joinMultiIndex'inizi tek bir Dizin'de birleştirmek istiyorsanız (sütunlarınızda yalnızca dize girişleri olduğunu varsayarak) şunları yapabilirsiniz:

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

Not: stripİkinci bir dizin olmadığında boşluk olması gerekir .

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) alternatif bir çözüm olabilir.
Tobias

8
bir küçük yorum ... sütun çok düzeyli birleştirmek için _ kullanmak istiyorsanız .. bunu kullanabilirsiniz ... df.columns = ['_'. df.columns içinde col için join (col) .strip (). değerleri]
ihightower

30
yalnızca birleştirilen kömürlerin alt çizgisini korumak için küçük değişiklik:['_'.join(col).rstrip('_') for col in df.columns.values]
Seiji Armstrong

Sadece ikinci sütun kullanımını istiyorsanız, bu harika çalıştı: df.columns = [col [1] df.columns.values ​​içinde col için]
user3078500

1
sum s_CDBunun yerine kullanmak istiyorsanız s_CD sum, bunu yapabilirsiniz 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
Bu çalışır, ancak programlı olarak erişilmesi zor ve sorgulanamayan sütun adlarının arkasında kalır
dmeu

1
Bu pandaların en son sürümüyle çalışmaz. 0.18 ile çalışıyor, ancak 0.20 ile çalışmıyor (şu andan itibaren en son)
TH22

1
@dmeu sütun adlarını korumak için pd.DataFrame(df.to_records(), columns=df.index.names + list(df.columns))
Teoretic

1
Sütun isimlerini benim için tuples olarak koruyor ve kullandığım indeksi korumak için:pd.DataFrame(df_volume.to_records(), index=df_volume.index).drop('index', axis=1)
Jayen

54

Bu konudaki mevcut cevapların tümü biraz tarihli olmalıdır. İtibariyle pandassürümü 0.24.0, .to_flat_index()neye ihtiyacınız yok.

Panda'nın kendi belgelerinden :

MultiIndex.to_flat_index ()

MultiIndex'i seviye değerlerini içeren Tuples Dizini'ne dönüştürün.

Belgelerinden basit bir örnek:

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'])

Uygulama to_flat_index():

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

Mevcut pandassütunu değiştirmek için kullanma

datSütunu olan bir DataFrame olan, nasıl kullanacağınıza bir örnek MultiIndex:

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

Andy Hayden'ın cevabı kesinlikle en kolay yoldur - yinelenen sütun etiketlerinden kaçınmak istiyorsanız, biraz ince ayar yapmanız gerekir

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
teşekkürler Theodros! Bu, tüm durumları ele alan tek doğru çözümdür!
CanCeylan

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

14

Toplama bilgilerinin herhangi birini, çoklu dizinin ikinci düzeyinden korumak istiyorsanız, bunu deneyebilirsiniz:

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_colstanımlanmamış.
samthebrand

11

mapFonksiyonu kullanmak için bunu yapmanın en pythonic yolu .

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

Çıktı 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')

F string ile Python 3.6+ kullanarak güncelleme:

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

print(df.columns)

Çıktı:

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

Benim için en kolay ve sezgisel çözüm, sütun adlarını get_level_values kullanarak birleştirmekti . Bu, aynı sütunda birden fazla toplama yaptığınızda yinelenen sütun adlarını önler:

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

Sütunlar arasında bir ayırıcı istiyorsanız, bunu yapabilirsiniz. Bu, Seiji Armstrong'un kabul edilen yanıta, yalnızca her iki dizin düzeyinde değer içeren sütunların alt çizgilerini içeren yorumuyla aynı şeyi döndürür:

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

Bu Andy Hayden'ın yukarıdaki büyük cevapla aynı şeyi yaptığını biliyorum, ama bu şekilde biraz daha sezgisel olduğunu ve hatırlanması daha kolay olduğunu düşünüyorum (bu yüzden bu konuya başvurmaya devam etmek zorunda değilim), özellikle acemi pandalar kullanıcıları için .

Bu yöntem aynı zamanda 3 sütun seviyeniz olabileceği durumda daha da genişletilebilir.

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

Bütün cevapları okuduktan sonra ben bunu buldum:

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

Kullanımı:

Bir veri çerçevesi verildiğinde:

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
  • Tek toplama yöntemi : kaynakla aynı adlı sonuç değişkenleri :

    df.groupby(by="grouper").agg("min").my_flatten_cols()
    • Aynı df.groupby(by="grouper", as_index = False) veya .agg(...)) (.reset_index
    • ----- before -----
                 val1  2
        grouper         
      
      ------ after -----
        grouper  val1  2
      0       x     0  1
      1       y     4  5
  • Tek kaynak değişkeni, birden çok toplama : istatistiğin adını taşıyan sonuç değişkenleri :

    df.groupby(by="grouper").agg({"val1": [min,max]}).my_flatten_cols("last")
    • İle aynı 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
  • Çoklu değişkenler, çoklu toplamalar : (varname) _ (statname) adlı sonuç değişkenleri :

    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)
    • a.columns = ["_".join(filter(None, map(str, levels))) for levels in a.columns.values]Kaputun altında çalışır (bu agg()sonuç biçimi MultiIndexsütunlarda olduğu için).
    • my_flatten_colsYardımcıya sahip değilseniz , bu durumda benzer şekilde çalışan (ancak sütunlarda sayısal etiketler varsa başarısız olur) @Seigi : tarafından önerilen çözümü yazmak daha kolay olabilir.a.columns = ["_".join(t).rstrip("_") for t in a.columns.values]
    • Sütunlardaki sayısal etiketleri işlemek için @jxstanford ve @Nolan Conaway ( a.columns = ["_".join(tuple(map(str, t))).rstrip("_") for t in a.columns.values]) tarafından önerilen çözümü kullanabilirsiniz , ancak tuple()aramanın neden gerekli olduğunu anlamıyorum ve rstrip()sadece bazı sütunların ("colname", "")( reset_index()düzeltmeye çalışmadan önce hangisi olabilir .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
  • El ile sonuçlanan değişkenleri isim istiyorum: (Bu edilir pandalar 0.20.0 beri kaldırılmış ile 0.23 itibariyle yeterli bir alternatif )

    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")
    • Diğer öneriler şunlardır : sütunları manuel olarak ayarlama: res.columns = ['A_sum', 'B_sum', 'count']veya .join()birden fazla groupbyifade girme .
    • ----- 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

Yardımcı işleviyle ele alınan vakalar

  • düzey adları dize olmayabilir, örn . Sütun adları tamsayı olduğunda pandaları DataFrame'i sütun numaralarına göre sıralayın , bu nedenlemap(str, ..)
  • onlar da boş olabilir, bu yüzden filter(None, ..)
  • tek düzeyli sütunlar için (yani, MultiIndex dışındaki herhangi bir şey), columns.valuesadları döndürür ( strtuples değil)
  • nasıl kullandığınıza bağlı olarak, .agg()bir sütun için en alt etiketi tutmanız veya birden çok etiketi birleştirmeniz gerekebilir
  • (Ben pandalar yeniyim beri?) daha sık değil, istediğim reset_index()çalışmak edebilmek için düzenli bir şekilde sütunlara grubuna-by, varsayılan olarak bunu yapmaz böylece

gerçekten büyük cevap, sen '[ "nin üzerinde çalıştığını açıklayabilir memnun edemiyorum " .join (tuple (harita (str, t))) Verileri, (" önceden, teşekkür a.columns.values içinde t için")]'
Vineet

@Vineet Yazıtımı, çözümümle benzer bir etkiye sahip olduğunu önermek için snippet'ten bahsettiğimi belirtmek için güncelledim. Neden tuple()gerekli olduğuna ilişkin ayrıntılar istiyorsanız, jxstanford'un gönderisine yorum yapmak isteyebilirsiniz. Aksi takdirde, incelemek yararlı olabilir .columns.valuessağlanan örnekte: [('val1', 'min'), (2, 'sum'), (2, 'size')]. 1) for t in a.columns.valuesikinci sütun için sütunların üzerinden döngüler t == (2, 'sum'); 2) her bir "seviye" için map(str, t)geçerlidir ; 3) sonuçları "2_sum",str()('2', 'sum')"_".join(('2','sum'))
Nickolay

5

Birden çok seviyeyi ve karışık türü ele alan genel bir çözüm:

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

1
Hiyerarşik olmayan sütunlar da varsa:df.columns = ['_'.join(tuple(map(str, t))).rstrip('_') for t in df.columns.values]
Nolan Conaway

Teşekkürler. Uzun zamandır arıyordum. Çok düzeyli dizinim tamsayı değerler içerdiğinden.
Sorunumu

4

Biraz geç olabilir, ancak yinelenen sütun adları hakkında endişelenmiyorsanız:

df.columns = df.columns.tolist()

Benim için bu, sütunların adlarını grup benzeri olacak şekilde değiştirir: (year, )ve(tempf, amax)
Nickolay

3

Düzeyler arasında adda bir ayırıcı olmasını istiyorsanız, bu işlev iyi çalışır.

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
Bunu sevdim. Sütunların hiyerarşik olmadığı durumdan ayrılmak bu çok basitleştirilebilir:df.columns = ["_".join(filter(None, c)) for c in df.columns]
Gigo

3

@Jxstanford ve @ tvt173'ü takip ederek, dize / int sütun adlarına bakılmaksızın hile yapması gereken hızlı bir işlev yazdım:

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

1

Aşağıdaki gibi de yapabilirsiniz. dfVeri çerçeveniz olmayı ve iki düzeyli bir dizin almayı düşünün (örneğin örnekte olduğu gibi)

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

1

Benim için işe yarayan basit bir yolu paylaşacağım.

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

0

MultiIndex'i diğer DataFrame yöntemleri zincirinin içinde düzleştirmek için, aşağıdaki gibi bir işlev tanımlayın:

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()

Ardından, bu işlevi DataFrame yöntemleri zincirine, zincirdeki diğer yöntemlerden önce ve sonra uygulamak için pipeyöntemi kullanın :groupbyagg

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

0

Başka bir basit rutin.

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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.