Pandas 按多列排名
Pandas rank by multiple columns
我正在尝试根据两列对 pandas 数据框进行排名。
我可以根据一列对其进行排名,但如何根据两列对其进行排名? 'SaleCount',然后 'TotalRevenue'?
import pandas as pd
df = pd.DataFrame({'TotalRevenue':[300,9000,1000,750,500,2000,0,600,50,500],
'Date':['2016-12-02' for i in range(10)],
'SaleCount':[10,100,30,35,20,100,0,30,2,20],
'shops':['S3','S2','S1','S5','S4','S8','S6','S7','S9','S10']})
df['Rank'] = df.SaleCount.rank(method='dense',ascending = False).astype(int)
#df['Rank'] = df.TotalRevenue.rank(method='dense',ascending = False).astype(int)
df.sort_values(['Rank'], inplace=True)
print(df)
当前输出:
Date SaleCount TotalRevenue shops Rank
1 2016-12-02 100 9000 S2 1
5 2016-12-06 100 2000 S8 1
3 2016-12-04 35 750 S5 2
2 2016-12-03 30 1000 S1 3
7 2016-12-08 30 600 S7 3
9 2016-12-10 20 500 S10 4
4 2016-12-05 20 500 S4 4
0 2016-12-01 10 300 S3 5
8 2016-12-09 2 50 S9 6
6 2016-12-07 0 0 S6 7
我正在尝试生成这样的输出:
Date SaleCount TotalRevenue shops Rank
1 2016-12-02 100 9000 S2 1
5 2016-12-02 100 2000 S8 2
3 2016-12-02 35 750 S5 3
2 2016-12-02 30 1000 S1 4
7 2016-12-02 30 600 S7 5
9 2016-12-02 20 500 S10 6
4 2016-12-02 20 500 S4 6
0 2016-12-02 10 300 S3 7
8 2016-12-02 2 50 S9 8
6 2016-12-02 0 0 S6 9
pd.factorize
将为可迭代的每个唯一元素生成唯一值。我们只需要按我们想要的顺序排序,然后分解。为了做多列,我们把排序后的结果转成元组。
cols = ['SaleCount', 'TotalRevenue']
tups = df[cols].sort_values(cols, ascending=False).apply(tuple, 1)
f, i = pd.factorize(tups)
factorized = pd.Series(f + 1, tups.index)
df.assign(Rank=factorized)
Date SaleCount TotalRevenue shops Rank
1 2016-12-02 100 9000 S2 1
5 2016-12-02 100 2000 S8 2
3 2016-12-02 35 750 S5 3
2 2016-12-02 30 1000 S1 4
7 2016-12-02 30 600 S7 5
4 2016-12-02 20 500 S4 6
9 2016-12-02 20 500 S10 6
0 2016-12-02 10 300 S3 7
8 2016-12-02 2 50 S9 8
6 2016-12-02 0 0 S6 9
另一种方法是 type-cast 两个感兴趣的列 str
并通过连接它们来组合它们。将这些转换回数值,以便可以根据它们的大小来区分它们。
在method=dense
中,重复值的排名将保持不变。 (此处:6)
由于您想按降序对它们进行排名,在 Series.rank()
中指定 ascending=False
可以让您获得所需的结果。
col1 = df["SaleCount"].astype(str)
col2 = df["TotalRevenue"].astype(str)
df['Rank'] = (col1+col2).astype(int).rank(method='dense', ascending=False).astype(int)
df.sort_values('Rank')
(根据 Nickil Maveli 的回答,对两个(非负)int 列进行排名的正确方法是将它们转换为字符串,连接它们并转换回 int。)
但是 如果您知道 TotalRevenue
被限制在某个范围内,这里有一个捷径 例如0 到 MAX_REVENUE=100,000 ;直接将它们作为非负整数进行操作:
df['Rank'] = (df['SaleCount']*MAX_REVENUE + df['TotalRevenue']).rank(method='dense', ascending=False).astype(int)
df.sort_values('Rank2')
通用的方法是将所需字段分组到元组中,无论类型如何。
df["Rank"] = df[["SaleCount","TotalRevenue"]].apply(tuple,axis=1)\
.rank(method='dense',ascending=False).astype(int)
df.sort_values("Rank")
TotalRevenue Date SaleCount shops Rank
1 9000 2016-12-02 100 S2 1
5 2000 2016-12-02 100 S8 2
3 750 2016-12-02 35 S5 3
2 1000 2016-12-02 30 S1 4
7 600 2016-12-02 30 S7 5
4 500 2016-12-02 20 S4 6
9 500 2016-12-02 20 S10 6
0 300 2016-12-02 10 S3 7
8 50 2016-12-02 2 S9 8
6 0 2016-12-02 0 S6 9
sort_values
+ GroupBy.ngroup
这将给出 dense
排名。
列应该在 groupby 之前按所需的顺序排序。在 groupby
中指定 sort=False
然后遵循此排序,以便组按照它们在排序的 DataFrame 中出现的顺序进行标记。
cols = ['SaleCount', 'TotalRevenue']
df['Rank'] = df.sort_values(cols, ascending=False).groupby(cols, sort=False).ngroup() + 1
输出:
print(df.sort_values('Rank'))
TotalRevenue Date SaleCount shops Rank
1 9000 2016-12-02 100 S2 1
5 2000 2016-12-02 100 S8 2
3 750 2016-12-02 35 S5 3
2 1000 2016-12-02 30 S1 4
7 600 2016-12-02 30 S7 5
4 500 2016-12-02 20 S4 6
9 500 2016-12-02 20 S10 6
0 300 2016-12-02 10 S3 7
8 50 2016-12-02 2 S9 8
6 0 2016-12-02 0 S6 9
此功能将按列列表连续排名,并支持按组排名(如果仅按多列对所有行进行排序则无法做到这一点)。
def rank_multicol(
df: pd.DataFrame,
rank_by: List[str],
group_by: Optional[List[str]] = None,
ascending: Union[List[bool], bool] = True,
rank_col_name: str = 'rank',
) - > pd.DataFrame:
df_aux = df.copy()
columns_to_group_by = [] if group_by is None else group_by
if type(ascending) is bool:
ascending = [ascending for _ in range(len(rank_by))]
elif len(ascending) != len(rank_by):
raise ValueError("`ascending` must be a scalar or have the same length of `rank_by`.")
for idx, feature in enumerate(rank_by):
# TODO: Optimize if no untying is required
if columns_to_group_by:
df_to_rank = df_aux.groupby(columns_to_group_by)
else:
df_to_rank = df_aux.copy()
ranks = (
df_to_rank
[feature]
.rank(ascending=ascending[idx], method='min')
.rename(rank_col_name)
)
if rank_col_name in df_aux:
df_aux[rank_col_name] = ranks + (df_aux[rank_col_name] - 1)
else:
df_aux[rank_col_name] = ranks
columns_to_group_by.append(feature)
return df_aux
我正在尝试根据两列对 pandas 数据框进行排名。 我可以根据一列对其进行排名,但如何根据两列对其进行排名? 'SaleCount',然后 'TotalRevenue'?
import pandas as pd
df = pd.DataFrame({'TotalRevenue':[300,9000,1000,750,500,2000,0,600,50,500],
'Date':['2016-12-02' for i in range(10)],
'SaleCount':[10,100,30,35,20,100,0,30,2,20],
'shops':['S3','S2','S1','S5','S4','S8','S6','S7','S9','S10']})
df['Rank'] = df.SaleCount.rank(method='dense',ascending = False).astype(int)
#df['Rank'] = df.TotalRevenue.rank(method='dense',ascending = False).astype(int)
df.sort_values(['Rank'], inplace=True)
print(df)
当前输出:
Date SaleCount TotalRevenue shops Rank
1 2016-12-02 100 9000 S2 1
5 2016-12-06 100 2000 S8 1
3 2016-12-04 35 750 S5 2
2 2016-12-03 30 1000 S1 3
7 2016-12-08 30 600 S7 3
9 2016-12-10 20 500 S10 4
4 2016-12-05 20 500 S4 4
0 2016-12-01 10 300 S3 5
8 2016-12-09 2 50 S9 6
6 2016-12-07 0 0 S6 7
我正在尝试生成这样的输出:
Date SaleCount TotalRevenue shops Rank
1 2016-12-02 100 9000 S2 1
5 2016-12-02 100 2000 S8 2
3 2016-12-02 35 750 S5 3
2 2016-12-02 30 1000 S1 4
7 2016-12-02 30 600 S7 5
9 2016-12-02 20 500 S10 6
4 2016-12-02 20 500 S4 6
0 2016-12-02 10 300 S3 7
8 2016-12-02 2 50 S9 8
6 2016-12-02 0 0 S6 9
pd.factorize
将为可迭代的每个唯一元素生成唯一值。我们只需要按我们想要的顺序排序,然后分解。为了做多列,我们把排序后的结果转成元组。
cols = ['SaleCount', 'TotalRevenue']
tups = df[cols].sort_values(cols, ascending=False).apply(tuple, 1)
f, i = pd.factorize(tups)
factorized = pd.Series(f + 1, tups.index)
df.assign(Rank=factorized)
Date SaleCount TotalRevenue shops Rank
1 2016-12-02 100 9000 S2 1
5 2016-12-02 100 2000 S8 2
3 2016-12-02 35 750 S5 3
2 2016-12-02 30 1000 S1 4
7 2016-12-02 30 600 S7 5
4 2016-12-02 20 500 S4 6
9 2016-12-02 20 500 S10 6
0 2016-12-02 10 300 S3 7
8 2016-12-02 2 50 S9 8
6 2016-12-02 0 0 S6 9
另一种方法是 type-cast 两个感兴趣的列 str
并通过连接它们来组合它们。将这些转换回数值,以便可以根据它们的大小来区分它们。
在method=dense
中,重复值的排名将保持不变。 (此处:6)
由于您想按降序对它们进行排名,在 Series.rank()
中指定 ascending=False
可以让您获得所需的结果。
col1 = df["SaleCount"].astype(str)
col2 = df["TotalRevenue"].astype(str)
df['Rank'] = (col1+col2).astype(int).rank(method='dense', ascending=False).astype(int)
df.sort_values('Rank')
(根据 Nickil Maveli 的回答,对两个(非负)int 列进行排名的正确方法是将它们转换为字符串,连接它们并转换回 int。)
但是 如果您知道 TotalRevenue
被限制在某个范围内,这里有一个捷径 例如0 到 MAX_REVENUE=100,000 ;直接将它们作为非负整数进行操作:
df['Rank'] = (df['SaleCount']*MAX_REVENUE + df['TotalRevenue']).rank(method='dense', ascending=False).astype(int)
df.sort_values('Rank2')
通用的方法是将所需字段分组到元组中,无论类型如何。
df["Rank"] = df[["SaleCount","TotalRevenue"]].apply(tuple,axis=1)\
.rank(method='dense',ascending=False).astype(int)
df.sort_values("Rank")
TotalRevenue Date SaleCount shops Rank
1 9000 2016-12-02 100 S2 1
5 2000 2016-12-02 100 S8 2
3 750 2016-12-02 35 S5 3
2 1000 2016-12-02 30 S1 4
7 600 2016-12-02 30 S7 5
4 500 2016-12-02 20 S4 6
9 500 2016-12-02 20 S10 6
0 300 2016-12-02 10 S3 7
8 50 2016-12-02 2 S9 8
6 0 2016-12-02 0 S6 9
sort_values
+ GroupBy.ngroup
这将给出 dense
排名。
列应该在 groupby 之前按所需的顺序排序。在 groupby
中指定 sort=False
然后遵循此排序,以便组按照它们在排序的 DataFrame 中出现的顺序进行标记。
cols = ['SaleCount', 'TotalRevenue']
df['Rank'] = df.sort_values(cols, ascending=False).groupby(cols, sort=False).ngroup() + 1
输出:
print(df.sort_values('Rank'))
TotalRevenue Date SaleCount shops Rank
1 9000 2016-12-02 100 S2 1
5 2000 2016-12-02 100 S8 2
3 750 2016-12-02 35 S5 3
2 1000 2016-12-02 30 S1 4
7 600 2016-12-02 30 S7 5
4 500 2016-12-02 20 S4 6
9 500 2016-12-02 20 S10 6
0 300 2016-12-02 10 S3 7
8 50 2016-12-02 2 S9 8
6 0 2016-12-02 0 S6 9
此功能将按列列表连续排名,并支持按组排名(如果仅按多列对所有行进行排序则无法做到这一点)。
def rank_multicol(
df: pd.DataFrame,
rank_by: List[str],
group_by: Optional[List[str]] = None,
ascending: Union[List[bool], bool] = True,
rank_col_name: str = 'rank',
) - > pd.DataFrame:
df_aux = df.copy()
columns_to_group_by = [] if group_by is None else group_by
if type(ascending) is bool:
ascending = [ascending for _ in range(len(rank_by))]
elif len(ascending) != len(rank_by):
raise ValueError("`ascending` must be a scalar or have the same length of `rank_by`.")
for idx, feature in enumerate(rank_by):
# TODO: Optimize if no untying is required
if columns_to_group_by:
df_to_rank = df_aux.groupby(columns_to_group_by)
else:
df_to_rank = df_aux.copy()
ranks = (
df_to_rank
[feature]
.rank(ascending=ascending[idx], method='min')
.rename(rank_col_name)
)
if rank_col_name in df_aux:
df_aux[rank_col_name] = ranks + (df_aux[rank_col_name] - 1)
else:
df_aux[rank_col_name] = ranks
columns_to_group_by.append(feature)
return df_aux