Birden çok yeni sütun oluşturmak için sütuna panda işlevi uygulanıyor mu?


215

Pandalarda nasıl yapılır:

extract_text_featuresBirden çok çıkış sütunları döndüren tek bir metin sütununda bir işlevi var . Özellikle, işlev 6 değer döndürür.

İşlev çalışır, ancak çıkışın doğru şekilde atanabilmesi için uygun bir dönüş türü (panda DataFrame / numpy dizisi / Python listesi) görünmemektedir. df.ix[: ,10:16] = df.textcol.map(extract_text_features)

Ben hiçbir zaman bu yineleme geri düşmesi gerektiğini düşünüyorum Yani df.iterrows()gereğince, bu ?

GÜNCELLEME: İle yineleme df.iterrows()en az 20 kat daha yavaştır, bu yüzden teslim oldum ve işlevi altı ayrı .map(lambda ...)aramaya ayırdım.

GÜNCELLEME 2: Bu soru v0.11.0 civarında sorulmuştur . Bu nedenle, soru ve cevapların çoğu fazla alakalı değildir.


1
Ben Birden atama bunu yazılı olan şekilde yapabilirsiniz sanmıyorum: df.ix[: ,10:16]. Bence mergeözelliklerinize veri setinde ihtiyacınız olacak.
Ocak 2013

1
Çok daha performanslı bir çözüm isteyenler için aşağıdakini kullanmayanı kontrol edinapply
Ted Petrou

Pandalarla yapılan sayısal işlemlerin çoğu vektörleştirilebilir - bu, geleneksel yinelemeden çok daha hızlı oldukları anlamına gelir. OTOH, bazı işlemlerin (string ve regex gibi) doğası gereği vektörleştirilmesi zordur. Bu durumda, verileriniz üzerinde nasıl döngü oluşturacağınızı anlamak önemlidir . Verilerinizde ne zaman ve nasıl döngü yapılacağı hakkında daha fazla bilgi için lütfen Pandalar ile döngüler için - Ne zaman umursamalıyım? Başlıklı konuyu okuyun. .
cs95

@coldspeed: asıl mesele, birkaç seçenek arasında daha yüksek performans olanı seçmemekti, bunun işe yaraması için pandaların sözdizimiyle savaşmaktı, v0.11.0 civarında .
smci

Gerçekten de, yorum yinelemeli çözümler arayan, ya daha iyisini bilmeyen ya da ne yaptığını bilen okuyuculara yöneliktir.
CS95

Yanıtlar:


109

User1827356'nın cevabını temel alarak, ödevi kullanarak bir geçişte atama yapabilirsiniz. df.merge :

df.merge(df.textcol.apply(lambda s: pd.Series({'feature1':s+1, 'feature2':s-1})), 
    left_index=True, right_index=True)

    textcol  feature1  feature2
0  0.772692  1.772692 -0.227308
1  0.857210  1.857210 -0.142790
2  0.065639  1.065639 -0.934361
3  0.819160  1.819160 -0.180840
4  0.088212  1.088212 -0.911788

EDIT: Büyük bellek tüketimi ve düşük hız lütfen unutmayın: https://ys-l.github.io/posts/2015/08/28/how-not-to-use-pandas-apply/ !


2
sadece merakla, bunu yaparak çok fazla bellek tüketmesi bekleniyor mu? Bunu 2.5mil satır tutan bir veri çerçevesi üzerinde yapıyorum ve neredeyse bellek sorunları (ayrıca sadece 1 sütun döndürmekten çok daha yavaş) koştu.
Jeffrey04

2
'df.join (df.textcol.apply (lambda s: pd.Series ({' özellik1 ': s + 1,' özellik2 ': s-1}))))' daha iyi bir seçenek olacağını düşünüyorum.
Shivam K. Thakkar

@ShivamKThakkar neden önerinizin daha iyi bir seçenek olacağını düşünüyorsunuz? Düşündüğünüzde daha verimli olur mu yoksa bellek maliyetiniz azalır mı?
tsando

1
Lütfen gerekli hızı ve belleği dikkate alın: ys-l.github.io/posts/2015/08/28/how-not-to-use-pandas-apply
Make42

190

Ben genellikle bunu kullanarak zip:

>>> df = pd.DataFrame([[i] for i in range(10)], columns=['num'])
>>> df
    num
0    0
1    1
2    2
3    3
4    4
5    5
6    6
7    7
8    8
9    9

>>> def powers(x):
>>>     return x, x**2, x**3, x**4, x**5, x**6

>>> df['p1'], df['p2'], df['p3'], df['p4'], df['p5'], df['p6'] = \
>>>     zip(*df['num'].map(powers))

>>> df
        num     p1      p2      p3      p4      p5      p6
0       0       0       0       0       0       0       0
1       1       1       1       1       1       1       1
2       2       2       4       8       16      32      64
3       3       3       9       27      81      243     729
4       4       4       16      64      256     1024    4096
5       5       5       25      125     625     3125    15625
6       6       6       36      216     1296    7776    46656
7       7       7       49      343     2401    16807   117649
8       8       8       64      512     4096    32768   262144
9       9       9       81      729     6561    59049   531441

8
Ancak 6 yerine 50 sütun eklediyseniz ne yaparsınız?
maksimum

14
@maxtemp = list(zip(*df['num'].map(powers))); for i, c in enumerate(columns): df[c] = temp[c]
ostrokach

8
@ostrokach Sanırım demek istedin for i, c in enumerate(columns): df[c] = temp[i]. Bu sayede gerçekten amacım var enumerate: D
rocarvaj

4
Bu, bunun için karşılaştığım en zarif ve okunabilir çözüm. Performans sorunlarıyla karşılaşmazsanız, deyim zip(*df['col'].map(function))muhtemelen gidilecek yoldur.
François Leblanc


84

Geçmişte yaptığım şey bu

df = pd.DataFrame({'textcol' : np.random.rand(5)})

df
    textcol
0  0.626524
1  0.119967
2  0.803650
3  0.100880
4  0.017859

df.textcol.apply(lambda s: pd.Series({'feature1':s+1, 'feature2':s-1}))
   feature1  feature2
0  1.626524 -0.373476
1  1.119967 -0.880033
2  1.803650 -0.196350
3  1.100880 -0.899120
4  1.017859 -0.982141

Tamlık için düzenleme

pd.concat([df, df.textcol.apply(lambda s: pd.Series({'feature1':s+1, 'feature2':s-1}))], axis=1)
    textcol feature1  feature2
0  0.626524 1.626524 -0.373476
1  0.119967 1.119967 -0.880033
2  0.803650 1.803650 -0.196350
3  0.100880 1.100880 -0.899120
4  0.017859 1.017859 -0.982141

concat (), yeni sütunları orijinal veri çerçevesine bağlamak için birleştirme () yönteminden daha basit görünür.
kimyon

2
güzel bir yanıt, uygulamanızın dışındaki sütunları belirtirseniz bir diksiyon veya birleştirme kullanmanıza gerek yokturdf[['col1', 'col2']] = df['col3'].apply(lambda x: pd.Series('val1', 'val2'))
Matt

66

Bu, kullanım durumlarının% 95'i için bunu yapmanın doğru ve en kolay yoludur:

>>> df = pd.DataFrame(zip(*[range(10)]), columns=['num'])
>>> df
    num
0    0
1    1
2    2
3    3
4    4
5    5

>>> def example(x):
...     x['p1'] = x['num']**2
...     x['p2'] = x['num']**3
...     x['p3'] = x['num']**4
...     return x

>>> df = df.apply(example, axis=1)
>>> df
    num  p1  p2  p3
0    0   0   0    0
1    1   1   1    1
2    2   4   8   16
3    3   9  27   81
4    4  16  64  256

yazmamalısın: df = df.apply (örnek (df), eksen = 1) yanılıyorsam beni düzelt, ben sadece bir acemi değilim
user299791

1
@ user299791, Hayır, bu durumda birinci sınıf bir nesne olarak örnek olarak davranırsınız, böylece işlevin kendisinden geçersiniz. Bu işlev her satıra uygulanır.
Michael David Watson

merhaba Michael, cevabın bana sorunumda yardımcı oldu. Kesinlikle çözümünüz orijinal pandaların df.assign () yönteminden daha iyidir, çünkü bu sütun başına bir defadır. Atama () kullanarak, 2 yeni sütun oluşturmak istiyorsanız, yeni sütun1 elde etmek için df üzerinde çalışmak için df1'i kullanmanız, ardından ikinci yeni sütun oluşturmak için df1 üzerinde çalışmak için df2'yi kullanmanız gerekir ... bu oldukça monotondur. Ama yöntemin hayatımı kurtardı !!! Teşekkürler!!!
commentallez-vous

1
Bu, sütun atama kodunu her satırda bir kez çalıştırmaz mı? pd.Series({k:v})Ewan'ın cevabındaki gibi sütun atamasını döndürmek ve serileştirmek daha iyi olmaz mıydı ?
Denis de Bernardy

Herkese yardımcı olursa, bu yaklaşım doğru ve sunulan tüm çözümlerin en basiti olsa da, satırı doğrudan bu şekilde güncellemek şaşırtıcı derecede yavaştı - 'expand' + pd.concat çözümleri ile uygulamaktan daha yavaş bir büyüklük sırası
Dmytro Bugayev

31

2018'de apply()argümanla kullanıyorumresult_type='expand'

>>> appiled_df = df.apply(lambda row: fn(row.text), axis='columns', result_type='expand')
>>> df = pd.concat([df, appiled_df], axis='columns')

6
Bugünlerde böyle yapıyorsunuz!
Make42

1
Bu, 2020'de kutudan çıktı, ancak başka pek çok soru olmadı. Ayrıca pd.Series performans sorunları ile ilgili her zaman hoş olan kullanmaz
Théo Rubenach

1
Bu iyi bir çözüm. Tek sorun, yeni eklenen 2 sütun için ad seçemezsiniz. Daha sonra df.rename (sütunlar = {0: 'col1', 1: 'col2'})
yapmanız gerekir

2
@pedrambashiri Geçtiğiniz işlev df.applya döndürürse dict, sütunlar tuşlara göre adlandırılır.
Seb

25

Sadece kullan result_type="expand"

df = pd.DataFrame(np.random.randint(0,10,(10,2)), columns=["random", "a"])
df[["sq_a","cube_a"]] = df.apply(lambda x: [x.a**2, x.a**3], axis=1, result_type="expand")

4
Bu seçeneğin 0.23'te yeni olduğuna işaret etmeye yardımcı olur . Soru sordu 0.11
smci

Güzel, bu basit ve hala düzgün çalışıyor. Aradığım şey bu. Teşekkürler
Isaac Sim

Önceki bir cevabı çoğaltır: stackoverflow.com/a/52363890/823470
katran

22

Özet: Yalnızca birkaç sütun oluşturmak istiyorsanız şunu kullanın:df[['new_col1','new_col2']] = df[['data1','data2']].apply( function_of_your_choosing(x), axis=1)

Bu çözüm için, oluşturduğunuz yeni sütun sayısının .apply () işlevine girdi olarak kullandığınız sayı sütunlarına eşit olması gerekir. Başka bir şey yapmak istiyorsanız, diğer cevaplara bir göz atın.

Ayrıntılar Diyelim ki iki sütunlu veri kareniz var. İlk sütun, bir kişinin 10 yaşındayken yüksekliğidir; ikincisi, 20 yaşındayken kişinin boyu söylenir.

Her bir kişinin boylarının ortalamasını ve her birinin boylarının toplamını hesaplamanız gerektiğini varsayalım. Bu, her satır için iki değerdir.

Bunu, yakında uygulanacak olan aşağıdaki işlevle yapabilirsiniz:

def mean_and_sum(x):
    """
    Calculates the mean and sum of two heights.
    Parameters:
    :x -- the values in the row this function is applied to. Could also work on a list or a tuple.
    """

    sum=x[0]+x[1]
    mean=sum/2
    return [mean,sum]

Bu işlevi şu şekilde kullanabilirsiniz:

 df[['height_at_age_10','height_at_age_20']].apply(mean_and_sum(x),axis=1)

(Açık olmak gerekirse: bu uygulama işlevi, alt kümelenmiş veri çerçevesindeki her satırdaki değerleri alır ve bir liste döndürür.)

Ancak, bunu yaparsanız:

df['Mean_&_Sum'] = df[['height_at_age_10','height_at_age_20']].apply(mean_and_sum(x),axis=1)

muhtemelen kaçınmak istediğiniz [ortalama, toplam] listelerini içeren 1 yeni sütun oluşturacaksınız, çünkü bu başka bir Lambda / Uygula gerektirecektir.

Bunun yerine, her bir değeri kendi sütununa bölmek istersiniz. Bunu yapmak için, aynı anda iki sütun oluşturabilirsiniz:

df[['Mean','Sum']] = df[['height_at_age_10','height_at_age_20']]
.apply(mean_and_sum(x),axis=1)

4
Pandalar 0.23 için sözdizimini kullanmanız gerekir:df["mean"], df["sum"] = df[['height_at_age_10','height_at_age_20']] .apply(mean_and_sum(x),axis=1)
SummerEla

Bu işlev hatayı artırabilir. Geri dönüş fonksiyonu return pd.Series([mean,sum])
Kanishk Mair

22

Benim için bu işe yaradı:

Df girişi

df = pd.DataFrame({'col x': [1,2,3]})
   col x
0      1
1      2
2      3

fonksiyon

def f(x):
    return pd.Series([x*x, x*x*x])

2 yeni sütun oluşturun:

df[['square x', 'cube x']] = df['col x'].apply(f)

Çıktı:

   col x  square x  cube x
0      1         1       1
1      2         4       8
2      3         9      27

13

Bunu yapmanın çeşitli yollarına baktım ve burada gösterilen yöntem (bir panda serisini döndürerek) en verimli görünmüyor.

Büyük bir rasgele veri veri çerçevesi ile başlarsak:

# Setup a dataframe of random numbers and create a 
df = pd.DataFrame(np.random.randn(10000,3),columns=list('ABC'))
df['D'] = df.apply(lambda r: ':'.join(map(str, (r.A, r.B, r.C))), axis=1)
columns = 'new_a', 'new_b', 'new_c'

Burada gösterilen örnek:

# Create the dataframe by returning a series
def method_b(v):
    return pd.Series({k: v for k, v in zip(columns, v.split(':'))})
%timeit -n10 -r3 df.D.apply(method_b)

10 döngü, döngü başına en iyi 3: 2,77 s

Alternatif bir yöntem:

# Create a dataframe from a series of tuples
def method_a(v):
    return v.split(':')
%timeit -n10 -r3 pd.DataFrame(df.D.apply(method_a).tolist(), columns=columns)

10 döngü, en iyi 3: döngü başına 8,85 ms

Benim tahminime göre bir dizi tuple alıp bunu bir DataFrame'e dönüştürmek çok daha verimli. Çalışmamda bir hata varsa, insanların düşüncelerini duymak isterim.


Bu gerçekten faydalı! İşlev döndüren seri yöntemlerine kıyasla 30 kat hız aldım.
Pushkar Nimkar

9

Kabul edilen çözüm, birçok veri için son derece yavaş olacaktır. En fazla oy sayısına sahip çözümün okunması biraz zor ve aynı zamanda sayısal verilerle yavaş. Her yeni sütun diğerlerinden bağımsız olarak hesaplanabilirse, her birini kullanmadan doğrudan atayacağım apply.

Sahte karakter verilerine sahip örnek

Bir DataFrame içinde 100.000 dize oluşturma

df = pd.DataFrame(np.random.choice(['he jumped', 'she ran', 'they hiked'],
                                   size=100000, replace=True),
                  columns=['words'])
df.head()
        words
0     she ran
1     she ran
2  they hiked
3  they hiked
4  they hiked

Orijinal soruda olduğu gibi bazı metin özelliklerini çıkarmak istediğimizi varsayalım. Örneğin, ilk karakteri çıkaralım, 'e' harfinin oluşumunu sayalım ve cümleyi büyük harfle yazalım.

df['first'] = df['words'].str[0]
df['count_e'] = df['words'].str.count('e')
df['cap'] = df['words'].str.capitalize()
df.head()
        words first  count_e         cap
0     she ran     s        1     She ran
1     she ran     s        1     She ran
2  they hiked     t        2  They hiked
3  they hiked     t        2  They hiked
4  they hiked     t        2  They hiked

zamanlamalar

%%timeit
df['first'] = df['words'].str[0]
df['count_e'] = df['words'].str.count('e')
df['cap'] = df['words'].str.capitalize()
127 ms ± 585 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

def extract_text_features(x):
    return x[0], x.count('e'), x.capitalize()

%timeit df['first'], df['count_e'], df['cap'] = zip(*df['words'].apply(extract_text_features))
101 ms ± 2.96 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

Şaşırtıcı bir şekilde, her bir değer arasında döngü yaparak daha iyi performans elde edebilirsiniz

%%timeit
a,b,c = [], [], []
for s in df['words']:
    a.append(s[0]), b.append(s.count('e')), c.append(s.capitalize())

df['first'] = a
df['count_e'] = b
df['cap'] = c
79.1 ms ± 294 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

Sahte sayısal veriler içeren başka bir örnek

1 milyon rastgele sayı oluşturun ve powersfonksiyonu yukarıdan test edin .

df = pd.DataFrame(np.random.rand(1000000), columns=['num'])


def powers(x):
    return x, x**2, x**3, x**4, x**5, x**6

%%timeit
df['p1'], df['p2'], df['p3'], df['p4'], df['p5'], df['p6'] = \
       zip(*df['num'].map(powers))
1.35 s ± 83.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

Her bir sütunu atamak 25 kat daha hızlı ve çok okunabilir:

%%timeit 
df['p1'] = df['num'] ** 1
df['p2'] = df['num'] ** 2
df['p3'] = df['num'] ** 3
df['p4'] = df['num'] ** 4
df['p5'] = df['num'] ** 5
df['p6'] = df['num'] ** 6
51.6 ms ± 1.9 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

Burada neden applygenellikle yolun böyle olmadığı hakkında daha fazla ayrıntı içeren benzer bir yanıt verdim.


8

Aynı cevabı diğer iki benzer soruya da gönderdiler. Bunu yapmayı tercih ettiğim yol, bir dizideki işlevin dönüş değerlerini sarmaktır:

def f(x):
    return pd.Series([x**2, x**3])

Ardından, ayrı sütunlar oluşturmak için aşağıdaki şekilde uygulayın:

df[['x**2','x**3']] = df.apply(lambda row: f(row['x']), axis=1)

1

değerler yerine tüm satırı döndürebilirsiniz:

df = df.apply(extract_text_features,axis = 1)

burada fonksiyon satırı döndürür

def extract_text_features(row):
      row['new_col1'] = value1
      row['new_col2'] = value2
      return row

Hayır extract_text_features, sadece metin sütununa df.textcol
df'nin

-2
def myfunc(a):
    return a * a

df['New Column'] = df['oldcolumn'].map(myfunc))

Bu benim için çalıştı. İşlenmiş eski sütun verileriyle yeni Sütun oluşturulacaktır.


2
Bu, 'birden çok yeni sütun' döndürmez
pedram bashiri

Bu, 'birden çok yeni sütun' döndürmez, bu nedenle soruyu cevaplamaz. Lütfen silebilir misiniz?
smci
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.