У меня есть фрейм данных в pandas.
pd.DataFrame({
"category": ["Transport", "Transport : Car", "Transport : Train", "Household", "Household : Utilities", "Household : Utilities : Water", "Household : Utilities : Electric", "Household : Cleaning", "Household : Cleaning : Bathroom", "Household : Cleaning : Kitchen", "Household : Rent", "Living", "Living : Other", "Living : Food", "Living : Something", "Living : Anitsomething"],
"amount": [5000, 4900, 100, 1100, 600, 400, 200, 100, 75, 25, 400, 250, 150, 100, 1000, -1000]
})
Категории и подкатегории разделены двоеточием.
Я пытаюсь отсортировать этот фрейм данных по убыванию (абсолютный стоимость) заказ. При соблюдении иерархической группировки. Т.е. отсортированный результат должен выглядеть так:
Transport 5000
Transport : Car 4900
Transport : Train 100
Household 1600
Household : Utilities 600
Household : Utilities : Water 400
Household : Utilities : Electric 200
Household : Rent 400
Living 250
Living : Something 1000
Living : Antisomething -1000
Living : Other 150
Living : Food 100
Я могу сделать это рекурсивно невероятно неэффективно. Очень медленный, но он работает.
def sort_hierachical(self, full_df, name_column, sort_column, parent="", level=0):
result_df = pd.DataFrame(columns=full_df.columns)
part_df = full_df.loc[(full_df[name_column].str.count(':') == level) & (full_df[name_column].str.startswith(parent)), :]
part_df['abs'] = part_df[sort_column].abs()
part_df = part_df.sort_values('abs', ascending=False)
for _, row in part_df.iterrows():
category = row[name_column]
row_df = pd.DataFrame(columns = full_df.columns).append(row)
child_rows = self.sort_hierachical(full_df, name_column, sort_column, category, level+1)
if not child_rows.empty:
result_df = pd.concat([result_df, row_df], sort=False)
result_df = pd.concat([result_df, child_rows], sort=False)
else:
result_df = pd.concat([result_df, row_df], sort=False)
return result_df
df = self.sort_hierachical(df, "category", "amount")
Мой вопрос: есть ли хороший эффективный способ сделать такую вещь в pandas. Какая-то групповая или многоиндексная уловка ??
Хорошая карма придет тем, кто может решить эту сложную задачу:)
Редактировать:
Это почти работает ... Но -1000, 1000 портит порядок сортировки.
def _sort_tree_df(self, df, tree_column, sort_column):
sort_key = sort_column + '_abs'
df[sort_key] = df[sort_column].abs()
df.index = pd.MultiIndex.from_frame(df[tree_column].str.split(":").apply(lambda x: [y.strip() for y in x]).apply(pd.Series))
sort_columns = [df[tree_column].values]
sort_columns.append(df[sort_key].values)
for x in range(df.index.nlevels, 0, -1):
group_lvl = list(range(0, x))
sort_columns.append(df.groupby(level=group_lvl)[sort_key].transform('max').values)
sort_indexes = np.lexsort(sort_columns)
df_sorted = df.iloc[sort_indexes[::-1]]
df_sorted.reset_index(drop=True, inplace=True)
df_sorted = df_sorted.drop(sort_key, axis=1)
return df_sorted
Edit2:
Хорошо, я думаю, мне удалось заставить это работать. Я до сих пор очень смущен тем, как работает lexsort. Я сделал эту работу путем образованного метода проб и ошибок. Если вы понимаете это, пожалуйста, не стесняйтесь объяснить это. Также не стесняйтесь опубликовать лучший метод.
def _sort_tree_df(self, df, tree_column, sort_column, delimeter=':'):
df.index = pd.MultiIndex.from_frame(df[tree_column].str.split(delimeter).apply(lambda x: [y.strip() for y in x]).apply(pd.Series))
sort_columns = [df[tree_column].values]
sort_columns.append(df[sort_column].abs().values)
for x in range(df.index.nlevels, 0, -1):
group_lvl = list(range(0, x))
sort_columns.append(df.groupby(level=group_lvl)[sort_column].transform('sum').abs().values)
sort_indexes = np.lexsort(sort_columns)
df_sorted = df.iloc[sort_indexes[::-1]]
df_sorted.reset_index(drop=True, inplace=True)
return df_sorted
Edit3 : На самом деле это не всегда правильно сортируется: (
Edit4 проблема в том, что мне нужен способ, чтобы преобразование th ('sum') применялось только к элементам, где уровень = x-1
ie что-то вроде:
df['level'] = df[tree_column].str.count(':')
sorting_by = df.groupby(level=group_lvl)[sort_column].transform('sum' if 'level' = x-1).abs().values
или
sorting_by = df.groupby(level=group_lvl).loc['level' = x-1: sort_column].transform('sum').abs().values
оба недопустимы
Кто-нибудь знает, как выполнить условное преобразование, подобное этому, для мультииндекса df?