在Python/Pandas中创建部分SAS PROC摘要替换
我们正在努力摆脱SAS,进入Python/Pandas。然而,有一件事我们遇到了麻烦,那就是为具有SAS例程灵活性的在Python/Pandas中创建部分SAS PROC摘要替换,python,pandas,Python,Pandas,我们正在努力摆脱SAS,进入Python/Pandas。然而,有一件事我们遇到了麻烦,那就是为具有SAS例程灵活性的PROC SUMMARY(又名PROC MEANS)创建一个替代品。对于非SAS用户:PROC SUMMARY只是一个例程,它在数据集中生成一个表,其中包含“所有观察值或观察值组内变量的描述性统计数据”,以解释SAS文档。我们的需求只是完整功能的一小部分—输出一个表,其中包含: 能够将不同的统计数据应用于不同的列(现在只需计数、求和、平均值、加权平均值) 能够处理零到多个分组变量
PROC SUMMARY
(又名PROC MEANS
)创建一个替代品。对于非SAS用户:PROC SUMMARY
只是一个例程,它在数据集中生成一个表,其中包含“所有观察值或观察值组内变量的描述性统计数据”,以解释SAS文档。我们的需求只是完整功能的一小部分—输出一个表,其中包含:
- 能够将不同的统计数据应用于不同的列(现在只需计数、求和、平均值、加权平均值)
- 能够处理零到多个分组变量
- 为加权平均值指定权重变量的能力
def wmean_ungrouped (d,w):
return (d.dot(w)).sum() / w.sum()
def wmean_grouped (group, var_name_in, var_name_weight):
d = group[var_name_in]
w = group[var_name_weight]
return (d * w).sum() / w.sum()
FUNCS = {
"mean" : np.mean ,
"sum" : np.sum ,
"count" : np.count_nonzero
}
def my_summary (
data ,
var_names_in ,
var_names_out ,
var_functions ,
var_name_weight = None ,
var_names_group = None
):
result = DataFrame()
if var_names_group is not None:
grouped = data.groupby (var_names_group)
for var_name_in, var_name_out, var_function in \
zip(var_names_in,var_names_out,var_functions):
if var_function == "wmean":
func = lambda x : wmean_grouped (x, var_name_in, var_name_weight)
result[var_name_out] = Series(grouped.apply(func))
else:
func = FUNCS[var_function]
result[var_name_out] = grouped[var_name_in].apply(func)
else:
for var_name_in, var_name_out, var_function in \
zip(var_names_in,var_names_out,var_functions):
if var_function == "wmean":
result[var_name_out] = \
Series(wmean_ungrouped(data[var_name_in], data[var_name_weight]))
else:
func = FUNCS[var_function]
result[var_name_out] = Series(func(data[var_name_in]))
return result
下面是对my\u summary()
函数的示例调用:
my_summary (
data=df,
var_names_in=["x_1","x_1","x_1","x_1"] ,
var_names_out=[
"x_1_c","x_1_s","x_1_m","x_1_wm"
] ,
var_functions=["count","sum","mean","wmean"] ,
var_name_weight="val_1" ,
var_names_group=["Region","Category"]
)
my_summary()
有效,但正如您所见,它的实现并不是最漂亮的。以下是主要问题:
- 两种不同的代码路径取决于分组或非分组-这完全是因为
和DataFrame
对单个列应用编程选择的缩减函数有不同的方法。对于DataFrameGroupBy
,我找到的唯一方法是直接调用DataFrame
<代码>数据[var\u name\u in].apply(func)不起作用,因为func(data[var\u name\u in])
系列上的
不会减少(与apply()
数据帧上的
不同)。另一方面,对于apply()
,我必须使用这种方法:DataFrameGroupBy
。这是因为像grouped[var\u name\u in].apply(func)
这样的东西不起作用(没有理由)func(grouped[var\u name\u in])
- 加权平均值的特殊处理-这是因为它在两列上运行,而所有其他计算只在一列上运行;我不知道这是否有帮助
- 两个不同的加权平均值函数-这是第一个问题的结果。未分组的函数具有
类型的参数,需要系列
进行乘法和减法运算;分组函数最终处理dot()
对象,并且必须使用序列groupby
运算符(对加权平均函数代码的确认)*
- 有没有熊猫特有的东西可以做到这一切(即扔掉上面的东西,改用它)
- 如果没有,上述问题是否有任何解决方案
- 很可能,有没有什么方法可以进行分组?也就是说,从
对象中获取DataFrame
对象,而无需对任何变量进行分组?然后,代码路径将减少,因为我们将专门处理DataFrameGroupBy
接口DataFrameGroupBy
groupby(lambda x:True)
。这是他发现的一个变通方法(顺便说一句,它的特点是Wes自己回答说需要一个DataFrame.agg()
,这将达到同样的目的)@JohnE的优秀解决方案允许我们专门处理类型为DataFrameGroupBy
的对象,并立即减少大部分代码路径。我能够使用一些功能性的噱头来进一步降低成本,这现在是可能的,因为我们只有DataFrameGroupBy
实例。基本上,所有函数都是根据需要生成的,“生成器”(此处引用以避免与Python生成器表达式混淆)有两个参数:值列名和权重列名,除wmean
外,第二个参数在所有情况下都会被忽略。生成的函数始终应用于整个DataFrameGroupBy
,就像最初的wmean
一样,参数是要使用的正确列名。我还用pandas计算替换了所有的np.*
实现,以便更好地处理NaN
值
除非有熊猫特有的东西可以做到这一点,否则这就是我们的解决方案:
FUNC_GENS = {
"mean" : lambda y,z : lambda x : x[y].mean(),
"sum" : lambda y,z : lambda x : x[y].sum() ,
"count" : lambda y,z : lambda x : x[y].count() ,
"wmean" : lambda y,z : lambda x : (x[y] * x[z]).sum() / x[z].sum()
}
def my_summary (
data ,
var_names_in ,
var_names_out ,
var_functions ,
var_name_weight = None ,
var_names_group = None ):
result = pd.DataFrame()
if var_names_group is None:
grouped = data.groupby (lambda x: True)
else:
grouped = data.groupby (var_names_group)
for var_name_in, var_name_out, var_function in \
zip(var_names_in,var_names_out,var_functions):
func_gen = FUNC_GENS[var_function]
func = func_gen (var_name_in, var_name_weight)
result[var_name_out] = grouped.apply(func)
return result
更新2019/当前解决方案
继我的原始帖子之后发布的pandas版本现在实现了大部分功能:
- 零分组-Wes M.过去曾说过需要一个and和一个and
- 多个列的多个聚合,并为输出列指定名称-其形式为
所以,基本上,除了加权平均数之外,其他都是。一个很好的当前解决方案是。好吧,这里有一个快速解决方案,可以解决两个问题(但仍然需要一个不同的加权平均值函数)。大多数情况下,它使用技巧(归功于@DSM)通过执行
groupby(lamda x:True)
绕过您的空组。如果在诸如手段之类的东西上有“权重”的夸克,那就太好了,但据我所知,没有。显然有一个基于numpy的加权分位数包,但我对此一无所知。伟大的项目顺便说一句
(请注意,名称与您的名称基本相同,我只是在wmean_grouped和my_summary中添加了一个“2”,否则您可以使用相同的调用接口)
正如旁白
df.descripe()
将为您提供状态摘要一样,这太棒了。groupby(lambda x:True)
hack只是一个技巧,开启了一个全新的可能性领域。我也很感谢您链接到groupby(None)
问题,其中包括韦斯本人的回答,他补充道
def wmean_grouped2 (group, var_name_in, var_name_weight):
d = group[var_name_in]
w = group[var_name_weight]
return (d * w).sum() / w.sum()
FUNCS = { "mean" : np.mean ,
"sum" : np.sum ,
"count" : np.count_nonzero }
def my_summary2 (
data ,
var_names_in ,
var_names_out ,
var_functions ,
var_name_weight = None ,
var_names_group = None ):
result = pd.DataFrame()
if var_names_group is None:
grouped = data.groupby (lambda x: True)
else:
grouped = data.groupby (var_names_group)
for var_name_in, var_name_out, var_function in \
zip(var_names_in,var_names_out,var_functions):
if var_function == "wmean":
func = lambda x : wmean_grouped2 (x, var_name_in, var_name_weight)
result[var_name_out] = pd.Series(grouped.apply(func))
else:
func = FUNCS[var_function]
result[var_name_out] = grouped[var_name_in].apply(func)
return result