Pandalar uygulamasından birden çok sütun döndür ()


120

Pandalarım var DataFrame df_test,. Bayt cinsinden boyutu temsil eden bir sütun 'boyut' içerir. Aşağıdaki kodu kullanarak KB, MB ve GB hesapladım:

df_test = pd.DataFrame([
    {'dir': '/Users/uname1', 'size': 994933},
    {'dir': '/Users/uname2', 'size': 109338711},
])

df_test['size_kb'] = df_test['size'].astype(int).apply(lambda x: locale.format("%.1f", x / 1024.0, grouping=True) + ' KB')
df_test['size_mb'] = df_test['size'].astype(int).apply(lambda x: locale.format("%.1f", x / 1024.0 ** 2, grouping=True) + ' MB')
df_test['size_gb'] = df_test['size'].astype(int).apply(lambda x: locale.format("%.1f", x / 1024.0 ** 3, grouping=True) + ' GB')

df_test


             dir       size       size_kb   size_mb size_gb
0  /Users/uname1     994933      971.6 KB    0.9 MB  0.0 GB
1  /Users/uname2  109338711  106,776.1 KB  104.3 MB  0.1 GB

[2 rows x 5 columns]

Bunu 120.000 satırın üzerinde çalıştırdım ve sütun başına yaklaşık 2.97 saniye * 3 = ~ 9 saniye,% timeit'e göre.

Bunu daha hızlı yapmamın bir yolu var mı? Örneğin, uygulamadan bir seferde bir sütun döndürüp 3 kez çalıştırmak yerine, orijinal veri çerçevesine geri eklemek için üç sütunun hepsini tek geçişte döndürebilir miyim?

Bulduğum diğer soruların hepsi birden çok değer almak ve tek bir değer döndürmek istiyor . Tek bir değer alıp birden çok sütun döndürmek istiyorum .

Yanıtlar:


141

Bu eski bir sorudur, ancak tamlık için, yeni verileri içeren uygulamalı işlevden bir Seri döndürebilir ve üç kez yineleme ihtiyacını ortadan kaldırabilirsiniz. Geçme axis=1uygulamak işlevine işlevini uygular sizesyeni dataframe eklemek için bir dizi dönen, dataframe her satıra. Bu seriler, yeni değerleri ve orijinal verileri içerir.

def sizes(s):
    s['size_kb'] = locale.format("%.1f", s['size'] / 1024.0, grouping=True) + ' KB'
    s['size_mb'] = locale.format("%.1f", s['size'] / 1024.0 ** 2, grouping=True) + ' MB'
    s['size_gb'] = locale.format("%.1f", s['size'] / 1024.0 ** 3, grouping=True) + ' GB'
    return s

df_test = df_test.append(rows_list)
df_test = df_test.apply(sizes, axis=1)

13
Doğru cevap olmadan yaklaşık 2 yıl geçirmesine şaşırdım. Başka bir şey arıyordum ve tökezledim. Umarım yararlı olmak için çok geç değildir!
Nelz11

12
rows_listBu cevapta ne var ?
David Stansby

Dataframe'i oluşturmak için yalnızca bir Seriler listesidir.
Nelz11

1
Pd.Series'in bir dizine ihtiyacı varsa, onu sağlamanız gerekir pd.Series(data, index=...). Aksi takdirde, sonucu ana veri çerçevesine geri atamaya çalıştığınızda şifreli hatalar alırsınız.
smci

117

Uygula'yı kullanın ve zip, Seri yolundan 3 kat daha hızlı olacaktır.

def sizes(s):    
    return locale.format("%.1f", s / 1024.0, grouping=True) + ' KB', \
        locale.format("%.1f", s / 1024.0 ** 2, grouping=True) + ' MB', \
        locale.format("%.1f", s / 1024.0 ** 3, grouping=True) + ' GB'
df_test['size_kb'],  df_test['size_mb'], df_test['size_gb'] = zip(*df_test['size'].apply(sizes))

Test sonucu:

Separate df.apply(): 

    100 loops, best of 3: 1.43 ms per loop

Return Series: 

    100 loops, best of 3: 2.61 ms per loop

Return tuple:

    1000 loops, best of 3: 819 µs per loop

1
Bunun daha fazla olumlu oy almamasına şaşırdım. Ek varyantı ve zamanlama verilerini paylaştığınız için teşekkür ederiz.
gumption

Demoyu nasıl iade ettiğinizi açıklar mısınız? En hızlı seçenek gibi görünüyor
Camilo

Lütfen örnek koduma bakın, bu tuple yolu.
Jesse

en hızlı ve en kolay gibi görünüyor. kendim bulamadığıma şaşırdım.
Shahir Ansari

67

Mevcut yanıtlardan bazıları iyi çalışıyor, ancak başka, belki daha fazla "pandifyed" seçeneği sunmak istiyorum. Bu benim için şu anki pandalar 0.23 ile çalışıyor (önceki sürümlerde çalışıp çalışmayacağından emin değilim):

import pandas as pd

df_test = pd.DataFrame([
  {'dir': '/Users/uname1', 'size': 994933},
  {'dir': '/Users/uname2', 'size': 109338711},
])

def sizes(s):
  a = locale.format("%.1f", s['size'] / 1024.0, grouping=True) + ' KB'
  b = locale.format("%.1f", s['size'] / 1024.0 ** 2, grouping=True) + ' MB'
  c = locale.format("%.1f", s['size'] / 1024.0 ** 3, grouping=True) + ' GB'
  return a, b, c

df_test[['size_kb', 'size_mb', 'size_gb']] = df_test.apply(sizes, axis=1, result_type="expand")

Püf noktasının result_typeparametresinde olduğuna dikkat edin apply, bu da sonucunu DataFramedoğrudan yeni / eski sütunlara atanabilecek bir şekilde genişletecektir .


1
Doğru ... pardon ... bazı kontrollerden sonra, bazı durumlarda 0.22 ile çalışıyor, ama sanal bir ortamdaydım ve bunu denediğimde aslında 0.23 çalıştırıyordum ...: /
jaumebonet

7
Bu en optimum cevaptır. Teşekkürler
AdR

Kabul! Mümkün olan her yerde modern pandalar.
sparc_spread

18

Okunabilir başka bir yol. Bu kod, üç yeni sütun ve değerlerini ekleyerek uygulama işlevinde kullanım parametreleri olmadan seriler döndürür.

def sizes(s):

    val_kb = locale.format("%.1f", s['size'] / 1024.0, grouping=True) + ' KB'
    val_mb = locale.format("%.1f", s['size'] / 1024.0 ** 2, grouping=True) + ' MB'
    val_gb = locale.format("%.1f", s['size'] / 1024.0 ** 3, grouping=True) + ' GB'
    return pd.Series([val_kb,val_mb,val_gb],index=['size_kb','size_mb','size_gb'])

df[['size_kb','size_mb','size_gb']] = df.apply(lambda x: sizes(x) , axis=1)

Genel bir örnek: https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.apply.html

df.apply(lambda x: pd.Series([1, 2], index=['foo', 'bar']), axis=1)

#foo  bar
#0    1    2
#1    1    2
#2    1    2

10

Gerçekten harika cevaplar! Jesse ve jaumebonet teşekkürler! Aşağıdakilerle ilgili bazı gözlemler:

  • zip(* ...
  • ... result_type="expand")

Genişletme biraz daha zarif olsa da ( yaygınlaştırılmış ), zip en az ** 2 kat daha hızlıdır . Aşağıdaki basit örnekte, 4 kat daha hızlı elde ettim .

import pandas as pd

dat = [ [i, 10*i] for i in range(1000)]

df = pd.DataFrame(dat, columns = ["a","b"])

def add_and_sub(row):
    add = row["a"] + row["b"]
    sub = row["a"] - row["b"]
    return add, sub

df[["add", "sub"]] = df.apply(add_and_sub, axis=1, result_type="expand")
# versus
df["add"], df["sub"] = zip(*df.apply(add_and_sub, axis=1))

10

Üst cevapları arasındaki performansı önemli zengindir ve Jesse & famaral42 zaten bu tartıştık, ama buna değer üst cevaplar arasında adil bir karşılaştırma paylaşımı ve Jesse'nin cevabın ince ama önemli ayrıntı tertipleyerek geçerli: geçirilen argüman işlevi, performansı da etkiler .

(Python 3.7.4, Pandalar 1.0.3)

import pandas as pd
import locale
import timeit


def create_new_df_test():
    df_test = pd.DataFrame([
      {'dir': '/Users/uname1', 'size': 994933},
      {'dir': '/Users/uname2', 'size': 109338711},
    ])
    return df_test


def sizes_pass_series_return_series(series):
    series['size_kb'] = locale.format_string("%.1f", series['size'] / 1024.0, grouping=True) + ' KB'
    series['size_mb'] = locale.format_string("%.1f", series['size'] / 1024.0 ** 2, grouping=True) + ' MB'
    series['size_gb'] = locale.format_string("%.1f", series['size'] / 1024.0 ** 3, grouping=True) + ' GB'
    return series


def sizes_pass_series_return_tuple(series):
    a = locale.format_string("%.1f", series['size'] / 1024.0, grouping=True) + ' KB'
    b = locale.format_string("%.1f", series['size'] / 1024.0 ** 2, grouping=True) + ' MB'
    c = locale.format_string("%.1f", series['size'] / 1024.0 ** 3, grouping=True) + ' GB'
    return a, b, c


def sizes_pass_value_return_tuple(value):
    a = locale.format_string("%.1f", value / 1024.0, grouping=True) + ' KB'
    b = locale.format_string("%.1f", value / 1024.0 ** 2, grouping=True) + ' MB'
    c = locale.format_string("%.1f", value / 1024.0 ** 3, grouping=True) + ' GB'
    return a, b, c

Sonuçlar burada:

# 1 - Accepted (Nels11 Answer) - (pass series, return series):
9.82 ms ± 377 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

# 2 - Pandafied (jaumebonet Answer) - (pass series, return tuple):
2.34 ms ± 48.6 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

# 3 - Tuples (pass series, return tuple then zip):
1.36 ms ± 62.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

# 4 - Tuples (Jesse Answer) - (pass value, return tuple then zip):
752 µs ± 18.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Uyarı nasıl dizilerini dönen en hızlı yöntemdir, ama ne geçirilir içinde bir argüman olarak da performansı etkiler. Koddaki fark belirsizdir ancak performans artışı önemlidir.

Test # 4 (tek bir değeri geçmek), gerçekleştirilen işlem görünüşte aynı olsa da, test # 3'ten (bir seriyi geçmek) iki kat daha hızlıdır.

Ama dahası var ...

# 1a - Accepted (Nels11 Answer) - (pass series, return series, new columns exist):
3.23 ms ± 141 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

# 2a - Pandafied (jaumebonet Answer) - (pass series, return tuple, new columns exist):
2.31 ms ± 39.3 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

# 3a - Tuples (pass series, return tuple then zip, new columns exist):
1.36 ms ± 58.4 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

# 4a - Tuples (Jesse Answer) - (pass value, return tuple then zip, new columns exist):
694 µs ± 3.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Bazı durumlarda (# 1a ve # 4a), işlevi çıktı sütunlarının zaten mevcut olduğu bir DataFrame'e uygulamak, bunları işlevden oluşturmaktan daha hızlıdır.

Testleri çalıştırmak için kod:

# Paste and run the following in ipython console. It will not work if you run it from a .py file.
print('\nAccepted Answer (pass series, return series, new columns dont exist):')
df_test = create_new_df_test()
%timeit result = df_test.apply(sizes_pass_series_return_series, axis=1)
print('Accepted Answer (pass series, return series, new columns exist):')
df_test = create_new_df_test()
df_test = pd.concat([df_test, pd.DataFrame(columns=['size_kb', 'size_mb', 'size_gb'])])
%timeit result = df_test.apply(sizes_pass_series_return_series, axis=1)

print('\nPandafied (pass series, return tuple, new columns dont exist):')
df_test = create_new_df_test()
%timeit df_test[['size_kb', 'size_mb', 'size_gb']] = df_test.apply(sizes_pass_series_return_tuple, axis=1, result_type="expand")
print('Pandafied (pass series, return tuple, new columns exist):')
df_test = create_new_df_test()
df_test = pd.concat([df_test, pd.DataFrame(columns=['size_kb', 'size_mb', 'size_gb'])])
%timeit df_test[['size_kb', 'size_mb', 'size_gb']] = df_test.apply(sizes_pass_series_return_tuple, axis=1, result_type="expand")

print('\nTuples (pass series, return tuple then zip, new columns dont exist):')
df_test = create_new_df_test()
%timeit df_test['size_kb'],  df_test['size_mb'], df_test['size_gb'] = zip(*df_test.apply(sizes_pass_series_return_tuple, axis=1))
print('Tuples (pass series, return tuple then zip, new columns exist):')
df_test = create_new_df_test()
df_test = pd.concat([df_test, pd.DataFrame(columns=['size_kb', 'size_mb', 'size_gb'])])
%timeit df_test['size_kb'],  df_test['size_mb'], df_test['size_gb'] = zip(*df_test.apply(sizes_pass_series_return_tuple, axis=1))

print('\nTuples (pass value, return tuple then zip, new columns dont exist):')
df_test = create_new_df_test()
%timeit df_test['size_kb'],  df_test['size_mb'], df_test['size_gb'] = zip(*df_test['size'].apply(sizes_pass_value_return_tuple))
print('Tuples (pass value, return tuple then zip, new columns exist):')
df_test = create_new_df_test()
df_test = pd.concat([df_test, pd.DataFrame(columns=['size_kb', 'size_mb', 'size_gb'])])
%timeit df_test['size_kb'],  df_test['size_mb'], df_test['size_gb'] = zip(*df_test['size'].apply(sizes_pass_value_return_tuple))

1
Performans özelliklerini de bozduğunuz için teşekkür ederiz!
PaulMest

1
Bu gerçekten ilginç ... Ve gerçekten düşünmek için biraz yiyecek. Ben her zaman bir kütüphanenin sağladığı çözümü kullanmayı tercih ederim, ancak onun performans farkı göz ardı edilemez. Şimdi merak ediyorum, eğer seri değil, sadece değer geçilirse çözümüm nasıl olur? Güzel analiz için teşekkürler!
jaumebonet

3

1.1 sürümünün buradaki en iyi yanıtta önerilen davranışı kırdığına inanıyorum.

import pandas as pd
def test_func(row):
    row['c'] = str(row['a']) + str(row['b'])
    row['d'] = row['a'] + 1
    return row

df = pd.DataFrame({'a': [1, 2, 3], 'b': ['i', 'j', 'k']})
df.apply(test_func, axis=1)

Yukarıdaki kod, Pandalar 1.1.0'da çalıştırıldı:

   a  b   c  d
0  1  i  1i  2
1  1  i  1i  2
2  1  i  1i  2

Pandalar 1.0.5'teyken geri döndü:

   a   b    c  d
0  1   i   1i  2
1  2   j   2j  3
2  3   k   3k  4

Bence beklediğiniz şey bu.

Sürüm notlarının bu davranışı nasıl açıkladığından emin değilim , ancak burada açıklandığı gibi orijinal satırları kopyalayarak mutasyondan kaçınmak eski davranışı yeniden canlandırıyor. yani:

def test_func(row):
    row = row.copy()   #  <---- Avoid mutating the original reference
    row['c'] = str(row['a']) + str(row['b'])
    row['d'] = row['a'] + 1
    return row

Kod örneğinizde bir kopyalama / yapıştırma hatası olabileceğini düşünüyorum. Kontrol edip göndermek istediğiniz şeyin bu olup olmadığını kontrol edebilir misiniz?
PaulMest

1
Teşekkürler @ PaulMest haklıydın. İki yazım hatasını düzelttim ve sorunun yanıtlandığı yere yeni bir bağlantı / referans ekledim.
moo

2
Stack Overflow'a hoş geldiniz! @moo
PaulMest

1

Genellikle, birden çok değer döndürmek için yaptığım şey bu

def gimmeMultiple(group):
    x1 = 1
    x2 = 2
    return array([[1, 2]])
def gimmeMultipleDf(group):
    x1 = 1
    x2 = 2
    return pd.DataFrame(array([[1,2]]), columns=['x1', 'x2'])
df['size'].astype(int).apply(gimmeMultiple)
df['size'].astype(int).apply(gimmeMultipleDf)

Bir veri çerçevesini döndürmenin kesin olarak avantajları vardır, ancak bazen gerekli değildir. Geri apply()dönüşlere bakabilir ve fonksiyonlarla biraz oynayabilirsiniz;)


Bu örnek için teşekkürler. Ancak bu, tüm sonuçlar için tek bir veri çerçevesi çıkarmaz. Orijinal veri çerçevesine geri eklemeye çalıştığımda, "ValueError: dizi doğru şekle yayınlanabilir değil" alıyorum.
PaulMest

Küçük bir veri örneği üretmek için kod sağlayabilir misiniz?
FooBar

Tabi ki. Orijinal gönderimdeki kodu örnek verileri ve çıktıyı içerecek şekilde güncelledim.
PaulMest

0

Orijinal olandan iki sütun içeren yeni bir veri çerçevesi verir.

import pandas as pd
df = ...
df_with_two_columns = df.apply(lambda row:pd.Series([row['column_1'], row['column_2']], index=['column_1', 'column_2']),axis = 1)
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.