小编典典

在Python / Pandas中创建部分SAS PROC Summary替换

python

我们正在努力脱离SAS,转而使用Python / Pandas。但是,我们遇到的一件事是创建具有SAS例程灵活性的PROC SUMMARY(AKA
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_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,我发现的唯一方法是直接调用func(data[var_name_in])data[var_name_in].apply(func)不起作用,因为apply()Series上不会减少(与apply()上不同DataFrame)。另一方面,对于DataFrameGroupBy,我必须使用这种方法:grouped[var_name_in].apply(func)。那是因为类似的东西func(grouped[var_name_in])将不起作用(没有理由应该这样做)。
  • 加权均值的特殊处理 -这是因为它在两列上运行,而与所有其他计算只在一个列上运行不同。我不知道这是否可以帮助您。
  • 两个不同的加权均值函数 -这是第一个问题的结果。未分组的函数具有Series-type参数,需要对其dot()进行乘法和缩减;分组的函数最终会处理SeriesGroupBy对象,并且必须使用*运算符(对于加权平均函数代码,对此SO职位的答复表示感谢。)

所以我的问题是:

  • 熊猫有没有一种可以做到所有这些功能的东西(即扔掉上面的东西,改用它)?
  • 如果不是,是否对上述任何问题进行了修复?
  • 碰巧有什么办法可以不进行任何分组-也就是说,从中获取DataFrameGroupBy对象DataFrame而不对任何变量进行分组吗?然后,将减少代码路径,因为我们将DataFrameGroupBy专门处理接口。

更新(旧-向下滚动至当前)

@JohnE的答案提供了一种按任何内容进行分组的方法: groupby(lambda x: True)。他在此SO帖子中发现了这种变通方法(顺便说一句,Wes亲自回答了对a的需求DataFrame.agg(),这将达到相同的目的)。@JohnE的出色解决方案使我们能够专门处理type的对象DataFrameGroupBy,并立即减少大多数代码路径。由于我们只有DataFrameGroupBy实例,因此我现在可以使用一些可行的功能来进一步减少操作。基本上,所有函数都是根据需要生成的-“生成器”(在此处加引号,以免与Python生成器表达式混淆)具有两个参数:值列名称和权重列名称,在所有情况下都将忽略其中的第二个参数wmean。生成的函数始终应用于整个DataFrameGroupBy,就像最初使用时一样wmean,其参数是要使用的正确列名。我还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年更新/当前解决方案

在我的原始文章之后发布的熊猫版本现在实现了以下大多数功能:

因此,除加权平均值外,基本上所有其他内容。一个好的当前解决方案在这里


阅读 220

收藏
2021-01-20

共1个答案

小编典典

好吧,这是一个可以解决两个问题的快捷方式(但对于加权均值仍然需要使用其他功能)。通常,它使用此处的技巧(贷记@DSM)来执行操作以避开空的组groupby(lamda x: True)。如果在手段之类的东西上存在“权重”的扭曲,但据我所知没有,那将是很棒的。显然有是提到加权位数包这里基于numpy的,但我不知道这件事。伟大的项目顺便说一句!

(请注意,名称与您的名称基本相同,我只是在wmean_grouped和my_summary中添加了“ 2”,否则您可以使用相同的调用接口)

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
2021-01-20