簡體   English   中英

如何按層次類別結構中的值對 pandas 中的 dataframe 進行排序

[英]How to sort dataframe in pandas by value in hierarchical category structure

我在 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

編輯2:

好的,我想我已經成功了。 我仍然很困惑 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 transform('sum') 僅適用於 level = x-1 的項目

即類似的東西:

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上進行這樣的條件轉換?

我不確定我是否完全理解了這個問題,但我認為您應該將列拆分為子類別,然后根據您想要的層次結構進行值排序。 像下面這樣的東西可能會完成這項工作。

使用以下內容創建新列:

for _, row in df.iterrows():
    for item, col in zip(row.category.split(':'), ['cat', 'sub_cat', 'sub_sub_cat']):
        df.loc[_, col] = item

然后對它們進行排序

df.sort_values(['cat', 'sub_cat', 'sub_sub_cat', 'amount'])

category    amount  cat     sub_cat     sub_sub_cat
3   Household   1100    Household   NaN     NaN
7   Household : Cleaning    100     Household   Cleaning    NaN
8   Household : Cleaning : Bathroom     75  Household   Cleaning    Bathroom
9   Household : Cleaning : Kitchen  25  Household   Cleaning    Kitchen
10  Household : Rent    400     Household   Rent    NaN
4   Household : Utilities   600     Household   Utilities   NaN
6   Household : Utilities : Electric    200     Household   Utilities   Electric
5   Household : Utilities : Water   400     Household   Utilities   Water
11  Living  250     Living  NaN     NaN
15  Living : Anitsomething  -1000   Living  Anitsomething   NaN
13  Living : Food   100     Living  Food    NaN
12  Living : Other  150     Living  Other   NaN
14  Living : Something  1000    Living  Something   NaN
0   Transport   5000    Transport   NaN     NaN
1   Transport : Car     4900    Transport   Car     NaN
2   Transport : Train   100     Transport   Train   Na

好的,花了一段時間才弄清楚,但現在我很確定這可行。 也比遞歸方法快得多。

def _sort_tree_df(self, df, tree_column, sort_column, delimeter=':'):
    df=df.copy()
    parts = df[tree_column].str.split(delimeter).apply(lambda x: [y.strip() for y in x]).apply(pd.Series)
    for i, column in enumerate(parts.columns):
        df[column] = parts[column]
    sort_columns = [df[tree_column].values]
    sort_columns.append(df[sort_column].abs().values)
    df['level'] = df[tree_column].str.count(':')
    for x in range(len(parts.columns), 0, -1):
        group_columns = list(range(0, x))
        sorting_by = df.copy()
        sorting_by.loc[sorting_by['level'] != x-1, sort_column] = np.nan
        sorting_by = sorting_by.groupby(group_columns)[sort_column].transform('sum').abs().values
        sort_columns.append(sorting_by)
    sort_indexes = np.lexsort(sort_columns)
    df_sorted = df.iloc[sort_indexes[::-1]]
    df_sorted.reset_index(drop=True, inplace=True)
    df.drop([column for column in parts.columns], inplace=True, axis=1)
    df.drop('level', inplace=True, axis=1)
    return df_sorted

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM