微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

我们正在努力摆脱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)不起作用,因为Series上的apply()没有减少(与DataFrame上的apply()不同).另一方面,对于DataFrameGroupBy,我必须使用这种方法:分组[var_name_in] .apply(func).这是因为像func(分组[var_name_in])之类的东西不起作用(没有理由应该这样做.)
>加权平均值的特殊处理 – 这是因为它在两列上运行,与所有其他计算不同,它只运行一列;我不知道这是否有帮助.
>两个不同的加权平均函数 – 这是第一个问题的结果.未分组函数具有Seri​​es类型参数,需要dot()来乘以和减少它们;分组函数最终处理SeriesGroupBy对象,并且必须使用*运算符(对于加权平均函数代码,确认为the answer to this SO post).

所以我的问题是:

>有没有可以做到这一切的熊猫原生的东西(即抛弃上面的东西并使用它)?
>如果没有,是否有任何修复上述任何问题?
>有任何机会,是否有一些方法可以分组 – 也就是说,从DataFrame获取DataFrameGroupBy对象而不对任何变量进行分组?然后代码路径将减少,因为我们将专门处理DataFrameGroupBy接口.

更新/当前解决方

@JohnE的回答提供了一种无需分组的方法:groupby(lambda x:True).这是一个解决方法,他发现了in this SO post(顺便提一下,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

解决方法:

好吧,这是一个快速解决两个问题(但仍然需要一个不同的加权平均函数).大多数情况下,它使用技巧here(归功于@DSM)通过groupby(lamda x:True)绕过你的空组.如果像手段这样的东西上有一个“重量”的kwarg会很棒,但据我所知,没有.显然有一个基于numpy的here加权分位数的包,但我对此一无所知.伟大的项目顺便说一句!

(请注意,名称与您的名称大致相同,我只是在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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐