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

spa*_*ead 9 python pandas

我们正在努力摆脱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
Run Code Online (Sandbox Code Playgroud)

以下是对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"]
)
Run Code Online (Sandbox Code Playgroud)

my_summary()有效,但正如你所看到的,它的实现并不是最漂亮的.以下是主要问题:

  • 两个不同的代码路径取决于分组或未分组 - 这完全取决于将编程选择的缩减功能应用于单个列的事实DataFrameDataFrameGroupBy不同的方式.因为DataFrame,我发现的唯一方法是直接调用func(data[var_name_in]).data[var_name_in].apply(func)不起作用,因为apply()在a Series不减少(不像apply()a DataFrame).另一方面,因为DataFrameGroupBy,我必须使用这种方法:grouped[var_name_in].apply(func).这是因为类似的东西func(grouped[var_name_in])不起作用(没有理由它.)
  • 加权平均值的特殊处理 - 这是因为它在两列上运行,与所有其他计算不同,它们只运行一列; 我不知道这是否有帮助.
  • 两个不同的加权平均函数 - 这是第一个问题的结果.未分组的函数具有Series-type参数,需要dot()乘以和减少它们; 分组函数最终处理SeriesGroupBy对象并且必须使用*运算符(对此SO帖子的答案的确认加权平均函数代码.)

所以我的问题是:

  • 是否存在可以完成所有这些操作的熊猫原生物(即丢弃上面的内容并使用它)?
  • 如果没有,是否有任何修复上述任何问题?
  • 通过任何机会,是否有某种方法可以分组 - 也就是说,DataFrameGroupByDataFrame没有分组的任何变量中获取对象?然后代码路径将减少,因为我们将DataFrameGroupBy专门处理接口.

更新/当前解决方案

@ JohnE的回答提供了一种无所事事的方式: groupby(lambda x: True).这是他在这篇SO帖子中发现的一种解决方法(顺便提一下,Wes自己的回答是关于a的需要DataFrame.agg(),它可以起到同样的作用).@ JohnE优秀的解决方案允许我们专门处理类型的对象DataFrameGroupBy,并立即减少大多数代码路径.我能够进一步减少使用现在可能的功能性噱头,因为我们只有DataFrameGroupBy实例.基本上,所有函数都是根据需要生成的 - "生成器"(在这里引用,以免与Python生成器表达式混淆)采用两个参数:值列名和权重列名,其中第二个在所有情况下都被忽略,除了wmean.生成的函数总是应用于整个DataFrameGroupBy,就像最初的情况一样wmean,参数是要使用的正确列名.我还np.*用pandas计算替换了所有实现,以更好地处理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
Run Code Online (Sandbox Code Playgroud)

Joh*_*hnE 4

好吧,这是一个快速解决两个问题的方法(但仍然需要不同的加权平均值函数)。大多数情况下,它使用这里的技巧(归功于@DSM)通过执行groupby(lamda x: True). 如果有一个 kwarg 来表示诸如平均值之类的东西的“权重”,那就太好了,但据我所知,没有。显然这里提到了一个基于 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
Run Code Online (Sandbox Code Playgroud)