在Python DataFrame中基于个体分数匹配对应国家日期的下一个更大四分位数得分
你遇到的问题确实没法用简单的pd.merge直接解决,因为需要做条件匹配(找到比当前Score大的最小Q_Score),而不是等值匹配。下面我会提供两种可行的方案,分别适合小数据量和大数据量的场景。
先确认数据(修正输入误差)
首先看你提供的df2数据,看起来是输入时的排版问题——每个国家应该对应4个四分位数(Q1-Q4),而不是按日期分散。我先修正df2的结构,确保每个国家的Q_Score是完整的一组:
import pandas as pd # 构造你的df1 df1_data = [ ["20220102", "A", "US", 12.6], ["20220103", "A", "US", 11.3], ["20220104", "A", "US", 13.2], ["20220105", "A", "US", 14.5], ["20220102", "B", "US", 9.8], ["20220103", "B", "US", 19.8], ["20220104", "B", "US", 12.3], ["20220105", "B", "US", 15.1], ["20220102", "C", "GB", 13.5], ["20220103", "C", "GB", 14.5], ["20220104", "C", "GB", 11.5], ["20220105", "C", "GB", 14.8], ] df1 = pd.DataFrame(df1_data, columns=["Date", "ID", "C", "Score"]) # 构造修正后的df2(每个国家对应4个四分位数) df2_data = [ ["US", 1, 10], ["US", 2, 13], ["US", 3, 16], ["US", 4, 20], ["GB", 1, 12], ["GB", 2, 13], ["GB", 3, 14], ["GB", 4, 15], ] df2 = pd.DataFrame(df2_data, columns=["C", "Q", "Q_Score"])
如果你的df2确实是每个日期+国家都有独立的四分位数,只需要在后续步骤中把分组键从C改成["Date", "C"]即可。
方案1:自定义函数+Apply(适合小数据量)
这种方法逻辑直观,容易理解,适合数据量不大的场景:
步骤1:定义匹配函数
写一个函数,输入当前Score和该国家的Q_Score列表,返回第一个比Score大的Q_Score;如果Score比所有Q_Score都大,就返回最大的Q_Score:
def find_next_higher(score, q_scores): sorted_q = sorted(q_scores) # 确保Q_Score从小到大排序 for q in sorted_q: if q > score: return q return sorted_q[-1] # 所有Q都小于等于Score时返回最大的
步骤2:构建国家-Q_Score映射字典
把df2按国家分组,得到每个国家对应的Q_Score列表:
country_q_map = df2.groupby("C")["Q_Score"].apply(list).to_dict()
步骤3:生成Q_Score列
用apply遍历df1的每一行,调用函数匹配对应的Q_Score:
df1["Q_Score"] = df1.apply(lambda row: find_next_higher(row["Score"], country_q_map[row["C"]]), axis=1) df3 = df1.copy()
此时df3就是你想要的结果,打印验证:
Date ID C Score Q_Score 0 20220102 A US 12.6 13 1 20220103 A US 11.3 13 2 20220104 A US 13.2 16 3 20220105 A US 14.5 16 4 20220102 B US 9.8 10 5 20220103 B US 19.8 20 6 20220104 B US 12.3 13 7 20220105 B US 15.1 16 8 20220102 C GB 13.5 14 9 20220103 C GB 14.5 15 10 20220104 C GB 11.5 12 11 20220105 C GB 14.8 15
方案2:Merge+Groupby(适合大数据量)
apply的效率较低,如果你的数据量很大,推荐用这种基于Pandas原生分组的方法,避免逐行遍历:
步骤1:合并两个DataFrame
先把df1和df2按国家合并,让每个Score都对应到该国家的所有Q_Score:
merged = df1.merge(df2, on="C", how="left")
步骤2:标记符合条件的Q_Score
新增一列标记哪些Q_Score大于当前Score:
merged["is_higher"] = merged["Q_Score"] > merged["Score"]
步骤3:分组筛选目标Q_Score
按df1的唯一行标识(Date+ID+C+Score)分组,筛选出符合条件的Q_Score,取最小的;如果没有符合条件的,就取最大的Q_Score:
df3 = merged.groupby(["Date", "ID", "C", "Score"]).apply( lambda x: x[x["is_higher"]]["Q_Score"].min() if x["is_higher"].any() else x["Q_Score"].max() ).reset_index(name="Q_Score")
这个方法的效率比apply高很多,适合处理百万级以上的数据。
内容的提问来源于stack exchange,提问作者fjurt

