【问题标题】:Groupwise sorting in pandas大熊猫中的分组排序
【发布时间】:2018-10-02 17:53:39
【问题描述】:

我想在另一个数组中定义的组边界内对一个数组进行排序。这些组不会以任何方式进行预排序,并且需要在排序后保持不变。在numpy 术语中,它看起来像这样:

import numpy as np

def groupwise_sort(group_idx, a, reverse=False):
    sortidx = np.lexsort((-a if reverse else a, group_idx))
    # Reverse sorting back to into grouped order, but preserving groupwise sorting
    revidx = np.argsort(np.argsort(group_idx, kind='mergesort'), kind='mergesort')
    return a[sortidx][revidx]

group_idx =   np.array([3, 2, 3, 2, 2, 1, 2, 1, 1])
a =           np.array([3, 2, 1, 7, 4, 5, 5, 9, 1])
groupwise_sort(group_idx, a)
# >>>            array([1, 2, 3, 4, 5, 1, 7, 5, 9])
groupwise_sort(group_idx, a, reverse=True)
# >>>            array([3, 7, 1, 5, 4, 9, 2, 5, 1])

我怎样才能对pandas 做同样的事情?我看到了df.groupby()df.sort_values(),尽管我找不到实现相同排序的直接方法。如果可能的话,还有一个快速的。

【问题讨论】:

  • 我认为如果解决方案在 numpy 中运行良好,则不需要 pandas 解决方案,主要是如果需要 fast 一个。或者使用pandas的原因是什么?
  • 我正在开发numpy_groupies,在numpynumba github.com/ml31415/numpy-groupies 之上的一堆聚合函数。为了比较和基准测试,我经常使用pandas
  • 关于快速使用它,这对我来说并不是超级关键,pandas 无论如何通常都很慢,所以我不使用它。我只是想避免因进行不公平的基准测试而受到指责。

标签: python pandas sorting pandas-groupby


【解决方案1】:

让我们先做好准备:

import pandas as pd
import numpy as np

group_idx =   np.array([3, 2, 3, 2, 2, 1, 2, 1, 1])
a =           np.array([3, 2, 1, 7, 4, 5, 5, 9, 1])

df = pd.DataFrame({'group': group_idx, 'values': a})
df
#   group  values
#0      3       3
#1      2       2
#2      3       1
#3      2       7
#4      2       4
#5      1       5
#6      2       5
#7      1       9
#8      1       1

获取按组和值(组内)排序的数据框:

df.sort_values(["group", "values"])

#   group  values
#8      1       1
#5      1       5
#7      1       9
#1      2       2
#4      2       4
#6      2       5
#3      2       7
#2      3       1
#0      3       3

要按降序对值进行排序,请使用ascending = False。要将不同的顺序应用于不同的列,您可以提供一个列表:

df.sort_values(["group", "values"], ascending = [True, False])

#   group  values
#7      1       9
#5      1       5
#8      1       1
#3      2       7
#6      2       5
#4      2       4
#1      2       2
#0      3       3
#2      3       1

这里,组按升序排序,每个组内的值按降序排序。

要仅对属于同一组的连续行的值进行排序,请创建一个新的组指示符:

(我将其保留在此处以供参考,因为它可能对其他人有帮助。在 OP 在 cmets 中澄清他的问题之前,我在早期版本中编写了此内容。)

df['new_grp'] = (df.group.diff(1) != 0).astype('int').cumsum()
df
#   group  values  new_grp
#0      3       3        1
#1      2       2        2
#2      3       1        3
#3      2       7        4
#4      2       4        4
#5      1       5        5
#6      2       5        6
#7      1       9        7
#8      1       1        7

然后我们可以轻松地使用new_grp 而不是group 进行排序,而不会改变组的原始顺序。

在组内排序,但保留指定组的行位置:

要对每个组的元素进行排序但保留数据框中特定于组的位置,我们需要跟踪原始行号。例如,以下内容可以解决问题:

# First, create an indicator for the original row-number:

df["ind"] = range(len(df))

# Now, sort the dataframe as before
df_sorted = df.sort_values(["group", "values"])

# sort the original row-numbers within each group
newindex = df.groupby("group").apply(lambda x: x.sort_values(["ind"]))["ind"].values

# assign the sorted row-numbers to the sorted dataframe
df_sorted["ind"] = newindex

# Sort based on the row-numbers:
sorted_asc = df_sorted.sort_values("ind")

# compare the resulting order of values with your desired output:
np.array(sorted_asc["values"])
# array([1, 2, 3, 4, 5, 1, 7, 5, 9])

当写在一个函数中时,这更容易测试和分析,所以让我们这样做:

def sort_my_frame(frame, groupcol = "group", valcol = "values", asc = True):

    frame["ind"] = range(len(frame))
    frame_sorted = frame.sort_values([groupcol, valcol], ascending = [True, asc])
    ind_sorted = frame.groupby(groupcol).apply(lambda x: x.sort_values(["ind"]))["ind"].values
    frame_sorted["ind"] = ind_sorted
    frame_sorted = frame_sorted.sort_values(["ind"])

    return(frame_sorted.drop(columns = "ind"))

np.array(sort_my_frame(df, "group", "values", asc = True)["values"])
# array([1, 2, 3, 4, 5, 1, 7, 5, 9])
np.array(sort_my_frame(df, "group", "values", asc = False)["values"])
# array([3, 7, 1, 5, 4, 9, 2, 5, 1])

请注意,后面的结果与您想要的结果相符。

我相信这可以用更简洁的方式写出来。例如,如果您的dataframe 的索引已经排序,您可以使用该索引代替我创建的指标ind(即,按照@DJK 的评论,我们可以使用sort_index 代替sort_values 并避免分配一个额外的列)。无论如何,上面强调了一种可能的解决方案以及如何处理它。另一种方法是使用您的numpy 函数并将输出包装在pd.DataFrame 周围。

【讨论】:

  • 好的,看起来我提供的不是最好的例子,因为我的 group_idx 已经排序。这个想法是,无论 group_idx 是什么,不假设它以任何方式排序,它应该在操作后保持不变。
  • 老实说,我还是不太明白这个基准的意义所在。 pandas 是建立在 numpy 之上的,所以我会惊讶地发现任何 numpy 解决方案都比 pandas 解决方案慢,因为 pandas 涉及额外的功能开销。
  • 我同意@coffeinjunky。当您需要访问较低级别的对象时,数据框和系列对象的 .values 属性会返回 numpy ndarrays。我也质疑这个练习的效用。您能否分享此功能的特定用例?我想不出一个。
  • 我本来打算发这个的,但是这个方法和你的df.groupby('group').apply(lambda x: x.sort_values(['values'])).sort_index(level=1).reset_index(drop=True)太相似了,你可以避免创建一个单独的列作为索引并删除一个排序语句和.values
  • @DJK 是的,我最初的想法类似。我创建新行指示器的原因是数据框可能有一个预先存在但(无论出于何种原因)未排序的索引(之后对索引进行排序会与 Michael 想要的输出混淆)。无论如何,问题仍然存在,在某些时候我们需要“打破”索引和值之间的映射(在组内)。
【解决方案2】:

Pandas 建立在 numpy 之上。假设这样的数据框:

df
Out[21]: 
   group  values
0      3       3
1      2       2
2      3       1
3      2       7
4      2       4
5      1       5
6      2       5
7      1       9
8      1       1

调用你的函数。

groupwise_sort(df.group.values, df['values'].values)
Out[22]: array([1, 2, 3, 4, 5, 1, 7, 5, 9])

groupwise_sort(df.group.values, df['values'].values, reverse=True)
Out[23]: array([3, 7, 1, 5, 4, 9, 2, 5, 1])

【讨论】:

  • 请注意,我不想想调用我的函数,只提供该函数以供参考。我正在寻找专门使用 pandas 内置函数的解决方案。
  • 没错!您还可以将该函数的结果包装到 pd.DataFrame 中,或者如果要将其保留为数据框,则将结果重新分配给初始列。
猜你喜欢
  • 2023-02-07
  • 2014-04-15
  • 2022-01-20
  • 2020-03-12
  • 2019-07-05
  • 2020-10-12
  • 1970-01-01
  • 2012-12-14
  • 2020-03-24
相关资源
最近更新 更多