Панды - Как сгладить иерархический индекс в столбцах
У меня есть фрейм данных с иерархическим индексом по оси 1 (столбцы) (из операции groupby.agg
):
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
Я хочу сгладить его, чтобы он выглядел следующим образом (имена не имеют решающего значения - я мог бы переименовать):
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
Как мне это сделать? (Я много пробовал, но безрезультатно.)
По предложению, вот голова в форме диктата
{('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}}
Ответы
Ответ 1
Я думаю, что самый простой способ сделать это - установить столбцы на верхний уровень:
df.columns = df.columns.get_level_values(0)
Примечание. Если на уровне есть имя, вы также можете получить к нему доступ, а не 0.
.
Если вы хотите объединить /join
ваш MultiIndex в один индекс (при условии, что у вас есть только строковые записи в столбцах), вы можете:
df.columns = [' '.join(col).strip() for col in df.columns.values]
Примечание: мы должны strip
пробел, если нет второго индекса.
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']
Ответ 2
pd.DataFrame(df.to_records()) # multiindex become columns and new index is integers only
Ответ 3
Ответ Энди Хайдена, безусловно, самый простой способ - если вы хотите избежать дублирования ярлыков столбцов, вам нужно немного настроить
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
Ответ 4
Все текущие ответы в этой теме должны быть немного устаревшими. Начиная с версии pandas
0.24.0, .to_flat_index()
делает то, что вам нужно.
Из панды собственной документации:
MultiIndex.to_flat_index()
Преобразуйте мультииндекс в индекс кортежей, содержащий значения уровня.
Простой пример из документации:
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'])
Применяем to_flat_index()
:
index.to_flat_index()
# Index([('foo', 'baz'), ('foo', 'qux'), ('bar', 'baz'), ('bar', 'qux')], dtype='object')
Использование его для замены существующего столбца pandas
Пример того, как вы будете использовать его в dat
, который представляет собой DataFrame со столбцом 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')
Ответ 5
df.columns = ['_'.join(tup).rstrip('_') for tup in df.columns.values]
Ответ 6
И если вы хотите сохранить любую информацию об агрегировании со второго уровня мультииндекса, вы можете попробовать следующее:
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
Ответ 7
Самый питонический способ сделать это - использовать функцию map
.
df.columns = df.columns.map(' '.join).str.strip()
Вывод на 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')
Обновление с использованием Python 3. 6+ с строкой f:
df.columns = [f'{f} {s}' if s != '' else f'{f}'
for f, s in df.columns]
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')
Ответ 8
Возможно, немного поздно, но если вас не беспокоят дублирующие имена столбцов:
df.columns = df.columns.tolist()
Ответ 9
Общее решение, которое обрабатывает несколько уровней и смешанных типов:
df.columns = ['_'.join(tuple(map(str, t))) for t in df.columns.values]
Ответ 10
Прочитав все ответы, я придумал следующее:
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
Применение:
С учетом кадра данных:
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
-
Единый метод агрегирования: результирующие переменные называются такими же, как источник:
df.groupby(by="grouper").agg("min").my_flatten_cols()
-
Одна переменная источника, множественные агрегации: результирующие переменные, названные в честь статистики:
df.groupby(by="grouper").agg({"val1": [min,max]}).my_flatten_cols("last")
-
Несколько переменных, множественные агрегации: результирующие переменные с именем (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)
-
Вы хотите назвать результирующие переменные вручную: (это не рекомендуется с момента pandas 0.20.0 без подходящей альтернативы на 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")
Случаи, выполняемые вспомогательной функцией
- имена уровней могут быть нестроковыми, например Index pandas DataFrame по номерам столбцов, когда имена столбцов являются целыми числами, поэтому нам нужно преобразовать с помощью
map(str,..)
- они также могут быть пустыми, поэтому мы должны
filter(None,..)
- для одноуровневых столбцов (т.е. ничего, кроме MultiIndex),
columns.values
возвращает имена (str
, а не кортежи) - в зависимости от того, как вы использовали
.agg()
вам может потребоваться сохранить нижнюю метку для столбца или объединить несколько ярлыков - (поскольку я новичок в pandas?) чаще, чем нет, я хочу, чтобы
reset_index()
мог работать с столбцами group-by обычным образом, поэтому он делает это по умолчанию
Ответ 11
Если вы хотите иметь разделитель в имени между уровнями, эта функция работает хорошо.
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)
Ответ 12
Следуя @jxstanford и @tvt173, я написал быструю функцию, которая должна делать трюк, независимо от имен столбцов string/int:
def flatten_cols(df):
df.columns = [
'_'.join(tuple(map(str, t))).rstrip('_')
for t in df.columns.values
]
return df
Ответ 13
Вы также можете сделать, как показано ниже. Рассмотрим df
как ваш фрейм данных и возьмите двухуровневый индекс (как в вашем примере)
df.columns = [(df.columns[i][0])+'_'+(datadf_pos4.columns[i][1]) for i in range(len(df.columns))]
Ответ 14
Я поделюсь прямым способом, который сработал для меня.
[" ".join([str(elem) for elem in tup]) for tup in df.columns.tolist()]
#df = df.reset_index() if needed
Ответ 15
Самым простым и интуитивно понятным решением для меня было объединение имен столбцов с помощью get_level_values . Это предотвращает дублирование имен столбцов, если в одном столбце выполняется несколько агрегаций:
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
Если вы хотите разделитель между столбцами, вы можете сделать это. Это вернет то же самое, что и комментарий Сейджи Армстронга к принятому ответу, который включает только подчеркивания для столбцов со значениями на обоих уровнях индекса:
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
Я знаю, что это делает то же самое, что и отличный ответ энди Хейдена выше, но я думаю, что это немного более интуитивно, и его легче запомнить (поэтому мне не нужно постоянно ссылаться на эту тему), особенно для начинающие пользователи панд.
Этот метод также более расширяем в случае, когда у вас может быть 3 уровня столбца.
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
Ответ 16
Чтобы сгладить MultiIndex внутри цепочки других методов DataFrame, определите такую функцию:
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()
Затем используйте метод pipe
чтобы применить эту функцию в цепочке методов DataFrame, после groupby
и agg
но перед любыми другими методами в цепочке:
my_df \
.groupby('group') \
.agg({'value': ['count']}) \
.pipe(flatten_index) \
.sort_values('value_count')