Grup nesnesine vs dönüştürmeyi uygulama


175

Aşağıdaki veri çerçevesini düşünün:

     A      B         C         D
0  foo    one  0.162003  0.087469
1  bar    one -1.156319 -1.526272
2  foo    two  0.833892 -1.666304
3  bar  three -2.026673 -0.322057
4  foo    two  0.411452 -0.954371
5  bar    two  0.765878 -0.095968
6  foo    one -0.654890  0.678091
7  foo  three -1.789842 -1.130922

Aşağıdaki komutlar çalışır:

> df.groupby('A').apply(lambda x: (x['C'] - x['D']))
> df.groupby('A').apply(lambda x: (x['C'] - x['D']).mean())

ancak aşağıdakilerden hiçbiri işe yaramaz:

> df.groupby('A').transform(lambda x: (x['C'] - x['D']))
ValueError: could not broadcast input array from shape (5) into shape (5,3)

> df.groupby('A').transform(lambda x: (x['C'] - x['D']).mean())
 TypeError: cannot concatenate a non-NDFrame object

Neden? Belgelerdeki örnek,transform bir grubu çağırmanın bir kişinin satır bazında işlem gerçekleştirmesine izin verdiğini düşündürmektedir :

# Note that the following suggests row-wise operation (x.mean is the column mean)
zscore = lambda x: (x - x.mean()) / x.std()
transformed = ts.groupby(key).transform(zscore)

Başka bir deyişle, dönüşümün esasen belirli bir uygulama türü olduğunu düşündüm (toplanmayan). Nerede yanılıyorum?

Referans olarak, yukarıdaki orijinal veri çerçevesinin yapısı aşağıdadır:

df = pd.DataFrame({'A' : ['foo', 'bar', 'foo', 'bar',
                          'foo', 'bar', 'foo', 'foo'],
                   'B' : ['one', 'one', 'two', 'three',
                         'two', 'two', 'one', 'three'],
                   'C' : randn(8), 'D' : randn(8)})

1
Aktarılan işlev transform, bağımsız değişkenle bir sayı, satır veya aynı şekli döndürmelidir. bir sayı ise, sayı gruptaki tüm öğelere ayarlanır, bir satırsa gruptaki tüm satırlara yayınlanır. Kodunuzda lambda işlevi, gruba yayınlanamayan bir sütun döndürür.
HYRY

1
Teşekkürler @HYRY, ama kafam karıştı. Yukarıda kopyaladığım (örn. İle zscore) belgelerdeki örneğe bakarsanız, transformher birinin xiçinde bir öğe olduğunu varsayan groupve ayrıca gruptaki öğe başına bir değer döndüren bir lambda işlevi alır . Neyi kaçırıyorum?
Amelio Vazquez-Reina

Son derece ayrıntılı bir çözüm arayanlar için aşağıya bakın .
Ted Petrou

@TedPetrou: tl; dr: 1) applytüm df'den geçer, ancak transformher sütunu ayrı ayrı bir Seri olarak geçirir. 2) applyherhangi bir şekil çıktısını döndürebilir (skaler / Seri / DataFrame / dizi / liste ...), oysa transformgrupla aynı uzunlukta bir dizi (1D Serisi / dizi / liste) döndürmelidir. Bu yüzden OP'nin buna ihtiyacı apply()yok transform(). Bu iyi bir soru çünkü doktor her iki farklılığı da net bir şekilde açıklamadı. ( apply/map/applymapveya diğer şeyler arasındaki
farka

Yanıtlar:


148

applyVe arasındaki iki büyük farktransform

transformVe applygroupby yöntemleri arasında iki büyük fark vardır .

  • Giriş:
    • applyörtük olarak, her grup için tüm sütunları DataFrame olarak özel işleve geçirir .
    • ise transformher grup için her sütunu ayrı ayrı bir Seri olarak özel işleve geçirir .
  • Çıktı:
    • applyAktarılan özel işlev bir skaler veya bir Seri veya DataFrame (veya numpy dizi veya çift liste) döndürebilir .
    • İletilen özel işlev , grupla aynı uzunlukta transformbir sıra (bir boyutlu Seri, dizi veya liste) döndürmelidir .

Yani, transformaynı anda sadece bir Seri applyüzerinde çalışır ve aynı anda tüm DataFrame üzerinde çalışır.

Özel işlevi inceleme

applyVeya özel işlevinize gönderilen girişi incelemek için biraz yardımcı olabilir transform.

Örnekler

Bazı örnek veriler oluşturalım ve grupları inceleyelim, böylece neden bahsettiğimi görebilirsiniz:

import pandas as pd
import numpy as np
df = pd.DataFrame({'State':['Texas', 'Texas', 'Florida', 'Florida'], 
                   'a':[4,5,1,3], 'b':[6,10,3,11]})

     State  a   b
0    Texas  4   6
1    Texas  5  10
2  Florida  1   3
3  Florida  3  11

Örtük olarak geçirilen nesnenin türünü yazdıran ve daha sonra yürütmenin durdurulabilmesi için bir hata oluşturan basit bir özel işlev oluşturalım.

def inspect(x):
    print(type(x))
    raise

Şimdi GroupBy hem bu işlevi geçmesine izin applyve transformnesne kendisine geçirilen olduğunu görmek için yöntemlerle:

df.groupby('State').apply(inspect)

<class 'pandas.core.frame.DataFrame'>
<class 'pandas.core.frame.DataFrame'>
RuntimeError

Gördüğünüz gibi, işleve bir DataFrame geçirilir inspect. DataFrame türünün neden iki kez yazdırıldığını merak ediyor olabilirsiniz. Pandalar ilk grubu iki kez yönetir. Bunu, hesaplamayı tamamlamak için hızlı bir yol olup olmadığını belirlemek için yapar. Bu, endişelenmemeniz gereken küçük bir ayrıntı.

Şimdi aynı şeyi yapalım transform

df.groupby('State').transform(inspect)
<class 'pandas.core.series.Series'>
<class 'pandas.core.series.Series'>
RuntimeError

Bir Seri geçti - tamamen farklı bir Pandalar nesnesi.

Bu nedenle, transformaynı anda yalnızca tek bir Seri ile çalışmasına izin verilir. Öyle değil aynı zamanda iki sütun üzerinde hareket etmek için imkansız. Biz denemek ve sütun çıkarma Yani, eğer agelen bbizim özel fonksiyonunun iç biz bir hatayı alacağı transform. Aşağıya bakınız:

def subtract_two(x):
    return x['a'] - x['b']

df.groupby('State').transform(subtract_two)
KeyError: ('a', 'occurred at index a')

Pandalar avar olmayan Series dizinini bulmaya çalışırken bir KeyError alırız . applyTüm DataFrame'e sahip olduğu için bu işlemi tamamlayabilirsiniz :

df.groupby('State').apply(subtract_two)

State     
Florida  2   -2
         3   -8
Texas    0   -2
         1   -5
dtype: int64

Çıktı bir Seridir ve orijinal dizin tutulduğu için biraz kafa karıştırıcıdır, ancak tüm sütunlara erişimimiz vardır.


Geçirilen pandalar nesnesini görüntüleme

Özel işlev içinde tüm pandalar nesnesini görüntülemek için daha da fazla yardımcı olabilir, böylece tam olarak neyle çalıştığınızı görebilirsiniz. Sen kullanabilirsiniz printkullanmak gibi I tarafından ifadeleri displaygelen işlevi IPython.displayDataFrames güzel bir jupyter dizüstü HTML arasında çıkış olsun böylece modül:

from IPython.display import display
def subtract_two(x):
    display(x)
    return x['a'] - x['b']

Ekran görüntüsü: resim açıklamasını buraya girin


Dönüşüm, grupla aynı boyutta tek boyutlu bir sıra döndürmelidir

Diğer fark, transformgrupla aynı boyutta tek boyutlu bir dizi döndürmesi gerektiğidir. Bu özel durumda, her grubun iki satırı vardır, bu nedenle iki satırlık transformbir sıra döndürmelidir. Başlamazsa, bir hata oluşur:

def return_three(x):
    return np.array([1, 2, 3])

df.groupby('State').transform(return_three)
ValueError: transform must return a scalar value for each group

Hata mesajı gerçekten sorunu tanımlayıcı değil. Bir diziyi grupla aynı uzunlukta döndürmeniz gerekir. Yani, böyle bir işlev işe yarayacaktır:

def rand_group_len(x):
    return np.random.rand(len(x))

df.groupby('State').transform(rand_group_len)

          a         b
0  0.962070  0.151440
1  0.440956  0.782176
2  0.642218  0.483257
3  0.056047  0.238208

Tek bir skaler nesnenin döndürülmesi, transform

Özel işlevinizden yalnızca tek bir skaler döndürürseniz, transformbunu gruptaki satırların her biri için kullanır:

def group_sum(x):
    return x.sum()

df.groupby('State').transform(group_sum)

   a   b
0  9  16
1  9  16
2  4  14
3  4  14

3
npTanımlanmadı. import numpy as npCevabınıza dahil ederseniz yeni başlayanların takdir edeceğini varsayıyorum .
Qaswed

188

.transformOperasyonla benzer şekilde karıştığımı hissettiğim .applyiçin, konuya biraz ışık tutan birkaç cevap buldum. Örneğin bu cevap çok yardımcı oldu.

Benim şimdiye kadar benim birbirinden izole (sütun) .transformile çalışacak (veya anlaşma) . Bunun anlamı, son iki çağrınızda:Series

df.groupby('A').transform(lambda x: (x['C'] - x['D']))
df.groupby('A').transform(lambda x: (x['C'] - x['D']).mean())

.transformİki sütundan değer almak istediniz ve 'it' aslında her ikisini aynı anda 'görmüyor' (tabiri caizse). transformveri çerçevesi sütunlarına tek tek bakacak ve tekrarlanan bir skaler dizisi (veya seri grubu) geri dönecektir len(input_column).

Yani .transformyapmak için kullanılması gereken bu skaler, Seriesbir girdiye uygulanan bir azaltma işlevinin sonucudur Series(ve bir seferde yalnızca bir seri / sütun).

Şu örneği düşünün (veri çerçevenizde):

zscore = lambda x: (x - x.mean()) / x.std() # Note that it does not reference anything outside of 'x' and for transform 'x' is one column.
df.groupby('A').transform(zscore)

getirecek:

       C      D
0  0.989  0.128
1 -0.478  0.489
2  0.889 -0.589
3 -0.671 -1.150
4  0.034 -0.285
5  1.149  0.662
6 -1.404 -0.907
7 -0.509  1.653

Bu, aynı anda yalnızca bir sütunda kullanacağınızla tamamen aynıdır:

df.groupby('A')['C'].transform(zscore)

getirili:

0    0.989
1   -0.478
2    0.889
3   -0.671
4    0.034
5    1.149
6   -1.404
7   -0.509

Not o .applyson örnekte ( df.groupby('A')['C'].apply(zscore)) tam olarak aynı şekilde çalışacaktır, ama bir dataframe üzerinde kullanmaya çalıştı eğer başarısız olur:

df.groupby('A').apply(zscore)

hata veriyor:

ValueError: operands could not be broadcast together with shapes (6,) (2,)

Peki başka nerede .transformfaydalıdır? En basit durum, azaltma işlevinin sonuçlarını orijinal veri çerçevesine geri atamaya çalışmaktır.

df['sum_C'] = df.groupby('A')['C'].transform(sum)
df.sort('A') # to clearly see the scalar ('sum') applies to the whole column of the group

getirili:

     A      B      C      D  sum_C
1  bar    one  1.998  0.593  3.973
3  bar  three  1.287 -0.639  3.973
5  bar    two  0.687 -1.027  3.973
4  foo    two  0.205  1.274  4.373
2  foo    two  0.128  0.924  4.373
6  foo    one  2.113 -0.516  4.373
7  foo  three  0.657 -1.179  4.373
0  foo    one  1.270  0.201  4.373

İle aynı çalışılıyor .applyverecekti NaNsiçinde sum_C. Çünkü nasıl geri yayınlanacağını bilmeyen .applybir azaltılmış dönecekti Series:

df.groupby('A')['C'].apply(sum)

vererek:

A
bar    3.973
foo    4.373

.transformVerileri filtrelemek için kullanıldığı durumlar da vardır :

df[df.groupby(['B'])['D'].transform(sum) < -1]

     A      B      C      D
3  bar  three  1.287 -0.639
7  foo  three  0.657 -1.179

Umarım bu biraz daha fazla netlik katar.


4
AMAN TANRIM. Fark çok incedir.
Dawei

3
.transform()eksik değerleri doldurmak için de kullanılabilir. Özellikle grup ortalamasını veya grup istatistiklerini NaNo gruptaki değerlere yayınlamak istiyorsanız . Ne yazık ki, panda belgeleri de bana yardımcı olmadı.
cyber-math

Bence son durumda, .groupby().filter()aynı şeyi yapıyor. Açıklaman için teşekkürler .apply()ve .transform()çok kafam karıştı.
Jiaxiang

neden df.groupby().transform()bir alt grup df için çalışamaz açıklar , her zaman hatayı alıyorum ValueError: transform must return a scalar value for each groupçünkü transformsütunları tek tek görüyor
jerrytim

Verileri filtrelemek için kullanılan son örneği .transform'u çok beğendim. süper güzel!
rishi jain

13

Farkı göstermek için çok basit bir snippet kullanacağım:

test = pd.DataFrame({'id':[1,2,3,1,2,3,1,2,3], 'price':[1,2,3,2,3,1,3,1,2]})
grouping = test.groupby('id')['price']

DataFrame şöyle görünür:

    id  price   
0   1   1   
1   2   2   
2   3   3   
3   1   2   
4   2   3   
5   3   1   
6   1   3   
7   2   1   
8   3   2   

Bu tabloda 3 müşteri kimliği vardır, her müşteri üç işlem yaptı ve her seferinde 1,2,3 dolar ödedi.

Şimdi, her müşteri tarafından yapılan minimum ödemeyi bulmak istiyorum. Bunu yapmanın iki yolu vardır:

  1. Kullanma apply:

    grouping.min ()

Geri dönüş şöyle:

id
1    1
2    1
3    1
Name: price, dtype: int64

pandas.core.series.Series # return type
Int64Index([1, 2, 3], dtype='int64', name='id') #The returned Series' index
# lenght is 3
  1. Kullanma transform:

    grouping.transform (dakika)

Geri dönüş şöyle:

0    1
1    1
2    1
3    1
4    1
5    1
6    1
7    1
8    1
Name: price, dtype: int64

pandas.core.series.Series # return type
RangeIndex(start=0, stop=9, step=1) # The returned Series' index
# length is 9    

Her iki yöntem de bir Seriesnesne döndürür , ancak lengthilki 3'tür ve lengthikincisi 9'dur.

Cevaplamak istiyorsanız What is the minimum price paid by each customer, applyyöntem seçmek için daha uygun olanıdır.

Cevaplamak What is the difference between the amount paid for each transaction vs the minimum paymentistiyorsanız, kullanmak istiyorsunuz transform, çünkü:

test['minimum'] = grouping.transform(min) # ceates an extra column filled with minimum payment
test.price - test.minimum # returns the difference for each row

Apply 3 boyutlu bir Seri döndürdüğü için burada çalışmaz, ancak orijinal df'nin uzunluğu 9'dur. Orijinal df'ye kolayca entegre edemezsiniz.


3
Bence bu harika bir cevap! Soru sorulduktan sonra dört yıldan fazla cevap vermek için zaman ayırdığınız için teşekkür ederiz!
Benjamin Dubreu

4
tmp = df.groupby(['A'])['c'].transform('mean')

gibi

tmp1 = df.groupby(['A']).agg({'c':'mean'})
tmp = df['A'].map(tmp1['c'])

veya

tmp1 = df.groupby(['A'])['c'].mean()
tmp = df['A'].map(tmp1)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.