여러 열을 사용하는 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.