여러 열을 사용하는 Pandas DataFrame 집계 함수


80

집계 DataFrame.agg되는 데이터의 둘 이상의 열에 액세스 할 수 있는 메서드에서 사용되는 집계 함수를 작성하는 방법이 있습니까? 일반적인 사용 사례는 가중 평균, 가중 표준 편차 함수입니다.

다음과 같이 쓸 수 있기를 바랍니다.

def wAvg(c, w):
    return ((c * w).sum() / w.sum())

df = DataFrame(....) # df has columns c and w, i want weighted average
                     # of c using w as weight.
df.aggregate ({"c": wAvg}) # and somehow tell it to use w column as weights ...

이 특정의 SO 질문을 해결 니스 기사 : pbpython.com/weighted-average.html
— ptim

답변:


104

예; .apply(...)각 하위에서 호출되는 함수를 사용합니다 DataFrame. 예를 들면 :

grouped = df.groupby(keys)

def wavg(group):
    d = group['data']
    w = group['weights']
    return (d * w).sum() / w.sum()

grouped.apply(wavg)

다음과 같은 몇 가지 작업으로 나누는 것이 더 효율적일 수 있습니다. (1) 가중치 열 생성, (2) 가중치로 관측 값 정규화, (3) 가중치 관측 값 그룹화 및 가중치 그룹화 합계 계산 , (4) 가중치 합으로 가중 관측치 합을 정규화합니다.
— kalu

4
df [ 'weights']를 제외한 모든 변수 (열)의 wavg를 계산하려면 어떻게해야합니까?
— CPBL 2014 년

2
@Wes, 어떤이 할 수있는 한 번 방법이 agg()과가 lambda중심으로 구축 np.average(...weights=...)이 게시물에 처음 나타난 이후, 또는 가중 수단 팬더의 새로운 네이티브 지원은?
— sparc_spread 2015-04-24

4
@Wes McKinney : 책에서 다음과 같은 접근 방식을 제안합니다. get_wavg = lambda g: np.average(g['data'], weights = g['weights']); grouped.apply(wavg) 둘은 서로 바꿔 사용할 수 있습니까?
— robroc

9

내 솔루션은 Nathaniel의 솔루션과 유사하지만 단일 열에 대한 것이며 매번 전체 데이터 프레임을 딥 복사하지 않아 엄청나게 느릴 수 있습니다. groupby (...). apply (...) 솔루션에 비해 성능 향상은 약 100x (!)입니다.

def weighted_average(df, data_col, weight_col, by_col):
    df['_data_times_weight'] = df[data_col] * df[weight_col]
    df['_weight_where_notnull'] = df[weight_col] * pd.notnull(df[data_col])
    g = df.groupby(by_col)
    result = g['_data_times_weight'].sum() / g['_weight_where_notnull'].sum()
    del df['_data_times_weight'], df['_weight_where_notnull']
    return result

PEP8을 지속적으로 사용하고 불필요한 del줄을 제거하면 더 읽기 쉽습니다 .
— MERose 19

감사! del내가 청소 그래서 내가, 성능을 향상시키기에 적절한 입력 DataFrame을 변경하기 때문에 라인은 실제로 불필요한 없습니다.
— ErnestScribbler

그러나 함수를 종료하는 바로 다음 줄에 결과를 반환합니다. 기능이 완료되면 모든 내부 개체가 제거됩니다.
— MERose

1
그러나 df는 내부 객체가 아닙니다. 함수에 대한 인수이며, 할당하지 않는 한 ( df = something) 얕은 사본으로 유지되고 제자리에서 변경됩니다. 이 경우 열이 DataFrame에 추가됩니다. 이 함수를 복사하여 붙여넣고 del행 없이 실행하고 열을 추가하여 주어진 DataFrame을 변경하는지 확인하십시오.
— ErnestScribbler

가중 평균은 여러 열의 집계에 대한 예일 뿐이므로 질문에 대한 답이 아닙니다.
— user__42

8

를 사용하여 groupby 개체에서 집계 된 값을 원하는만큼 반환 할 수 apply있습니다. 간단히 Series를 반환하면 인덱스 값이 새 열 이름이됩니다.

간단한 예를 보겠습니다.

df = pd.DataFrame({'group':['a','a','b','b'],
                   'd1':[5,10,100,30],
                   'd2':[7,1,3,20],
                   'weights':[.2,.8, .4, .6]},
                 columns=['group', 'd1', 'd2', 'weights'])
df

  group   d1  d2  weights
0     a    5   7      0.2
1     a   10   1      0.8
2     b  100   3      0.4
3     b   30  20      0.6

에 전달 될 사용자 지정 함수를 정의합니다 apply. data매개 변수가 DataFrame임을 의미하는 DataFrame을 암시 적으로 허용합니다 . agggroupby 메서드 에서는 불가능한 여러 열을 사용하는 방법 에 유의하십시오.

def weighted_average(data):
    d = {}
    d['d1_wa'] = np.average(data['d1'], weights=data['weights'])
    d['d2_wa'] = np.average(data['d2'], weights=data['weights'])
    return pd.Series(d)

apply사용자 지정 함수를 사용 하여 groupby 메서드를 호출합니다 .

df.groupby('group').apply(weighted_average)

       d1_wa  d2_wa
group              
a        9.0    2.2
b       58.0   13.2

다른 답변에서 설명한대로 가중치 합계를 새 DataFrame 열로 미리 계산하여 더 나은 성능을 얻을 수 있으며 함께 사용하지 마십시오 apply.


4

다음 (Wes McKinney의 답변을 기반으로 함)은 내가 찾고 있던 것을 정확하게 수행합니다. .NET에서이 작업을 수행하는 더 간단한 방법이 있는지 배우게되어 기쁩니다 pandas.

def wavg_func(datacol, weightscol):
    def wavg(group):
        dd = group[datacol]
        ww = group[weightscol] * 1.0
        return (dd * ww).sum() / ww.sum()
    return wavg


def df_wavg(df, groupbycol, weightscol):
    grouped = df.groupby(groupbycol)
    df_ret = grouped.agg({weightscol:sum})
    datacols = [cc for cc in df.columns if cc not in [groupbycol, weightscol]]
    for dcol in datacols:
        try:
            wavg_f = wavg_func(dcol, weightscol)
            df_ret[dcol] = grouped.apply(wavg_f)
        except TypeError:  # handle non-numeric columns
            df_ret[dcol] = grouped.agg({dcol:min})
    return df_ret

이 함수 df_wavg()는 "groupby"열로 그룹화 된 데이터 프레임을 반환하고 가중치 열에 대한 가중치의 합계를 반환합니다. 다른 열은 가중 평균이거나 숫자가 아닌 경우 min()함수가 집계에 사용됩니다.


4

나는 이것을 많이하고 다음이 매우 편리하다는 것을 알았다.

def weighed_average(grp):
    return grp._get_numeric_data().multiply(grp['COUNT'], axis=0).sum()/grp['COUNT'].sum()
df.groupby('SOME_COL').apply(weighed_average)

이것은 모든 숫자 열의 가중 평균을 계산하고 df숫자가 아닌 열을 삭제합니다.


이것은 타오르는 빠른 속도입니다! 잘 했어!
— Shay Ben-Sasson 2016

열이 여러 개인 경우 정말 좋습니다. 좋은!
— 크리스

@santon, 답변 주셔서 감사합니다. 솔루션의 예를 들어 주실 수 있습니까? 솔루션을 사용하는 동안 'KeyError :'COUNT '오류가 발생했습니다.
— Allen

@Allen 가중 평균에 사용하려는 개수가있는 열 이름은 무엇이든 사용해야합니다.
— santon

4

이 비아를 달성하는 것은 groupby(...).apply(...)성과가 없습니다. 여기 내가 항상 사용하는 솔루션이 있습니다 (본질적으로 kalu의 논리를 사용).

def grouped_weighted_average(self, values, weights, *groupby_args, **groupby_kwargs):
   """
    :param values: column(s) to take the average of
    :param weights_col: column to weight on
    :param group_args: args to pass into groupby (e.g. the level you want to group on)
    :param group_kwargs: kwargs to pass into groupby
    :return: pandas.Series or pandas.DataFrame
    """

    if isinstance(values, str):
        values = [values]

    ss = []
    for value_col in values:
        df = self.copy()
        prod_name = 'prod_{v}_{w}'.format(v=value_col, w=weights)
        weights_name = 'weights_{w}'.format(w=weights)

        df[prod_name] = df[value_col] * df[weights]
        df[weights_name] = df[weights].where(~df[prod_name].isnull())
        df = df.groupby(*groupby_args, **groupby_kwargs).sum()
        s = df[prod_name] / df[weights_name]
        s.name = value_col
        ss.append(s)
    df = pd.concat(ss, axis=1) if len(ss) > 1 else ss[0]
    return df

pandas.DataFrame.grouped_weighted_average = grouped_weighted_average

1
실적이 없다고 말할 때. 차이는 얼마입니까? 측정 했습니까?
— Bouncner
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.