Gấu trúc - Cách làm phẳng chỉ mục phân cấp trong các cột


325

Tôi có khung dữ liệu với chỉ mục phân cấp trong trục 1 (cột) (từ một groupby.aggthao tác):

     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

Tôi muốn làm phẳng nó, để nó trông như thế này (tên không quan trọng - tôi có thể đổi tên):

     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

Làm thế nào để tôi làm điều này? (Tôi đã cố gắng rất nhiều, nhưng không có kết quả.)

Theo một gợi ý, đây là cái đầu ở dạng dict

{('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
bạn có thể thêm đầu ra df[:5].to_dict()làm ví dụ cho người khác đọc trong tập dữ liệu của bạn không?
Zelazny7

Ý tưởng tốt. Đã làm nó ở trên vì nó quá dài cho bình luận.
Ross R

Có một gợi ý về trình pandastheo dõi vấn đề để thực hiện một phương pháp dành riêng cho việc này.
joelostblom

2
@joelostblom và trên thực tế nó đã được thực hiện (gấu trúc 0.24.0 trở lên). Tôi đã đăng một câu trả lời nhưng về cơ bản bây giờ bạn chỉ có thể làm dat.columns = dat.columns.to_flat_index(). Chức năng gấu trúc tích hợp.
chỉ có

Câu trả lời:


471

Tôi nghĩ cách dễ nhất để làm điều này là đặt các cột ở mức cao nhất:

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

Lưu ý: nếu cấp độ có tên, bạn cũng có thể truy cập bằng tên này, thay vì 0.

.

Nếu bạn muốn kết hợp / joinMulti Index của bạn thành một Chỉ mục (giả sử bạn chỉ có các mục nhập chuỗi trong các cột của mình), bạn có thể:

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

Lưu ý: chúng ta phải có stripkhoảng trắng khi không có chỉ mục thứ hai.

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) có thể là một giải pháp thay thế.
Tobias

8
một nhận xét nhỏ ... nếu bạn muốn sử dụng _ cho đa cấp cột kết hợp .. bạn có thể sử dụng ... df.columns = ['_'. tham gia (col) .strip () cho col trong df.columns. giá trị]
ihightower

30
sửa đổi nhỏ để duy trì gạch dưới chỉ dành cho cols tham gia:['_'.join(col).rstrip('_') for col in df.columns.values]
Seiji Armstrong

Điều này hoạt động rất tốt, nếu bạn chỉ muốn sử dụng cột thứ hai: df.columns = [col [1] cho col trong df.columns.values]
user3078500

1
Nếu bạn muốn sử dụng sum s_CDthay vì s_CD sum, người ta có thể làm 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
Điều này hoạt động, nhưng để lại các tên cột khó truy cập theo chương trình và không thể truy vấn được
dmeu

1
Điều này sẽ không hoạt động với phiên bản mới nhất của gấu trúc. Nó hoạt động với 0,18 nhưng không phải với 0,20 (
muộn

1
@dmeu để giữ tên cột pd.DataFrame(df.to_records(), columns=df.index.names + list(df.columns))
Teoretic

1
Nó bảo toàn tên cột là bộ dữ liệu cho tôi và để giữ chỉ mục tôi sử dụng:pd.DataFrame(df_volume.to_records(), index=df_volume.index).drop('index', axis=1)
Jayen

54

Tất cả các câu trả lời hiện tại về chủ đề này phải có một chút ngày. Kể từ pandasphiên bản 0.24.0, .to_flat_index()những gì bạn cần.

Từ tài liệu riêng của gấu trúc :

Multi Index.to_flat_index ()

Chuyển đổi Multi Index thành Index của Tuples chứa các giá trị cấp độ.

Một ví dụ đơn giản từ tài liệu của nó:

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

Áp dụng to_flat_index():

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

Sử dụng nó để thay thế pandascột hiện có

Một ví dụ về cách bạn sử dụng nó dat, đó là DataFrame với một MultiIndexcột:

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

Câu trả lời của Andy Hayden chắc chắn là cách dễ nhất - nếu bạn muốn tránh các nhãn cột trùng lặp, bạn cần phải điều chỉnh một chút

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
cảm ơn Theodros! Đây là giải pháp đúng duy nhất xử lý tất cả các trường hợp!
CanCeylan

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

14

Và nếu bạn muốn giữ lại bất kỳ thông tin tổng hợp nào từ cấp độ thứ hai của multiindex, bạn có thể thử điều này:

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_colskhông được xác định.
samthebrand 7/10/2015

11

Cách pythonic nhất để làm điều này để sử dụng mapchức năng.

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

Đầu ra 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')

Cập nhật bằng Python 3.6+ với chuỗi f:

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

print(df.columns)

Đầu ra:

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

Giải pháp đơn giản và trực quan nhất đối với tôi là kết hợp các tên cột bằng get_level_values . Điều này ngăn các tên cột trùng lặp khi bạn thực hiện nhiều tập hợp trên cùng một cột:

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

Nếu bạn muốn phân cách giữa các cột, bạn có thể làm điều này. Điều này sẽ trả về điều tương tự như nhận xét của Seiji Armstrong về câu trả lời được chấp nhận chỉ bao gồm dấu gạch dưới cho các cột có giá trị ở cả hai cấp chỉ mục:

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

Tôi biết điều này cũng giống như câu trả lời tuyệt vời của Andy Hayden ở trên, nhưng tôi nghĩ rằng cách này trực quan hơn một chút và dễ nhớ hơn (vì vậy tôi không phải tiếp tục đề cập đến chủ đề này), đặc biệt là đối với người dùng gấu trúc mới làm quen .

Phương pháp này cũng có thể mở rộng hơn trong trường hợp bạn có thể có 3 cấp độ cột.

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

Sau khi đọc qua tất cả các câu trả lời, tôi đã nghĩ ra điều này:

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

Sử dụng:

Đưa ra một khung dữ liệu:

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
  • Phương pháp tổng hợp đơn : các biến kết quả được đặt tên giống như nguồn :

    df.groupby(by="grouper").agg("min").my_flatten_cols()
    • Tương tự như df.groupby(by="grouper", as_index = Sai) hoặc .agg(...).reset_index ()
    • ----- before -----
                 val1  2
        grouper         
      
      ------ after -----
        grouper  val1  2
      0       x     0  1
      1       y     4  5
  • Biến nguồn đơn, nhiều tập hợp : biến kết quả được đặt tên theo thống kê :

    df.groupby(by="grouper").agg({"val1": [min,max]}).my_flatten_cols("last")
    • Tương tự như 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
  • Nhiều biến, nhiều tập hợp : kết quả biến có tên (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)
    • Chạy a.columns = ["_".join(filter(None, map(str, levels))) for levels in a.columns.values]dưới mui xe (vì hình thức agg()kết quả này MultiIndextrên các cột).
    • Nếu bạn không có trình my_flatten_colstrợ giúp, có thể dễ dàng nhập vào giải pháp được đề xuất bởi @Seigi : a.columns = ["_".join(t).rstrip("_") for t in a.columns.values], hoạt động tương tự trong trường hợp này (nhưng không thành công nếu bạn có nhãn số trên các cột)
    • Để xử lý các nhãn số trên các cột, bạn có thể sử dụng giải pháp được đề xuất bởi @jxstanford và @Nolan Conaway ( a.columns = ["_".join(tuple(map(str, t))).rstrip("_") for t in a.columns.values]), nhưng tôi không hiểu tại sao tuple()cuộc gọi lại cần thiết và tôi tin rằng rstrip()chỉ bắt buộc nếu một số cột có mô tả như ("colname", "")( Điều này có thể xảy ra nếu bạn reset_index()trước khi cố gắng sửa chữa .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
  • Bạn muốn đặt tên các biến kết quả bằng tay: (điều này được phản kể từ gấu trúc 0.20.0 với không thay thế đầy đủ tính 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")
    • Các đề xuất khác bao gồm : đặt các cột theo cách thủ công: res.columns = ['A_sum', 'B_sum', 'count']hoặc nhập .join()nhiều groupbycâu lệnh.
    • ----- 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

Các trường hợp được xử lý bởi chức năng của người trợ giúp

  • tên cấp có thể không phải là chuỗi, ví dụ: Index pandas DataFrame theo số cột, khi tên cột là số nguyên , vì vậy chúng tôi phải chuyển đổi bằngmap(str, ..)
  • chúng cũng có thể trống, vì vậy chúng ta phải filter(None, ..)
  • đối với các cột một cấp (tức là bất cứ thứ gì ngoại trừ Multi Index), columns.valuessẽ trả về các tên ( strchứ không phải các bộ dữ liệu)
  • tùy thuộc vào cách bạn sử dụng, .agg()bạn có thể cần giữ nhãn dưới cùng cho một cột hoặc ghép nhiều nhãn
  • (vì tôi mới biết về gấu trúc?) thường xuyên hơn không, tôi muốn reset_index()có thể làm việc với các cột theo nhóm theo cách thông thường, do đó, theo mặc định, nó sẽ làm điều đó

câu trả lời thực sự tuyệt vời, bạn có thể vui lòng giải thích hoạt động của '[" " .join (tuple (map (str, t))). rstrip (" ") cho t trong a.columns.values]', cảm ơn trước
Vine

@Vineet Tôi đã cập nhật bài đăng của mình để cho biết rằng tôi đã đề cập đến đoạn trích đó để đề xuất rằng nó có tác dụng tương tự với giải pháp của tôi. Nếu bạn muốn biết chi tiết về lý do tuple()cần thiết, bạn có thể muốn bình luận về bài đăng của jxstanford. Mặt khác, có thể hữu ích để kiểm tra .columns.valuestrong ví dụ được cung cấp : [('val1', 'min'), (2, 'sum'), (2, 'size')]. 1) for t in a.columns.valuescác vòng trên các cột, cho cột thứ hai t == (2, 'sum'); 2) map(str, t)áp dụng str()cho từng "cấp độ", dẫn đến ('2', 'sum'); 3) "_".join(('2','sum'))kết quả là "2_sum",
Nickolay

5

Một giải pháp chung xử lý nhiều cấp độ và loại hỗn hợp:

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

1
Trong trường hợp cũng có các cột không phân cấp:df.columns = ['_'.join(tuple(map(str, t))).rstrip('_') for t in df.columns.values]
Nolan Conaway

Cảm ơn. Đã tìm kiếm trong một thời gian dài. Vì chỉ số đa cấp của tôi chứa các giá trị nguyên. Nó đã giải quyết vấn đề của tôi :)
AnksG

4

Có thể hơi muộn, nhưng nếu bạn không lo lắng về các tên cột trùng lặp:

df.columns = df.columns.tolist()

Đối với tôi, điều này thay đổi tên của các cột thành giống như tuple: (year, )(tempf, amax)
Nickolay

3

Trong trường hợp bạn muốn có một dấu phân cách trong tên giữa các cấp, chức năng này hoạt động tốt.

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
Tôi thích nó. Thoát khỏi trường hợp các cột không được phân cấp, điều này có thể được đơn giản hóa rất nhiều:df.columns = ["_".join(filter(None, c)) for c in df.columns]
Gigo

3

Theo dõi @jxstanford và @ tvt173, tôi đã viết một hàm nhanh để thực hiện thủ thuật, bất kể tên cột / chuỗi int:

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

1

Bạn cũng có thể làm như dưới đây. Hãy coi dflà khung dữ liệu của bạn và giả sử chỉ số hai cấp (như trường hợp trong ví dụ của bạn)

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

1

Tôi sẽ chia sẻ một cách đơn giản mà làm việc cho tôi.

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

0

Để làm phẳng Multi Index bên trong một chuỗi các phương thức DataFrame khác, hãy xác định một hàm như thế này:

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

Sau đó, sử dụng pipephương thức để áp dụng hàm này trong chuỗi các phương thức DataFrame, sau groupbyaggtrước bất kỳ phương thức nào khác trong chuỗi:

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

0

Một thói quen đơn giản khác.

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.