कई नए कॉलम बनाने के लिए कॉलम में पांडा फ़ंक्शन लागू करें?


215

पंडों में यह कैसे करें:

मेरे पास एक extract_text_featuresएकल टेक्स्ट कॉलम पर एक फ़ंक्शन है , कई आउटपुट कॉलम लौटाता है। विशेष रूप से, फ़ंक्शन 6 मान लौटाता है।

फ़ंक्शन काम करता है, हालाँकि ऐसा प्रतीत नहीं होता है कि कोई उचित रिटर्न प्रकार (पांडा डेटाफ़्रेम / संख्यात्मक सरणी / पायथन सूची) जैसे कि आउटपुट सही ढंग से सौंपा जा सकता है df.ix[: ,10:16] = df.textcol.map(extract_text_features)

तो मुझे लगता है मैं के साथ पुनरावृत्ति करने के लिए वापस छोड़ने की ज़रूरत df.iterrows(), के अनुसार इस ?

अद्यतन: df.iterrows()कम से कम 20x धीमी गति से, इसलिए मैंने आत्मसमर्पण कर दिया और फ़ंक्शन को छह अलग-अलग .map(lambda ...)कॉलों में विभाजित किया ।

अद्यतन 2: यह प्रश्न v0.11.0 के आसपास पूछा गया था । इसलिए बहुत से प्रश्न और उत्तर भी प्रासंगिक नहीं हैं।


1
मुझे नहीं लगता कि आप जिस तरह से यह लिखा है आप कई कार्य कर सकते हैं df.ix[: ,10:16]:। मुझे लगता है कि आपको mergeडेटासेट में अपनी सुविधाओं के लिए जाना होगा ।
ज़ेलज़नी 7

1
बहुत अधिक प्रदर्शन करने वाले समाधान चाहने वालों के लिए यह नीचे की जाँच करें जो उपयोग नहीं करता हैapply
टेड पेट्रो

पंडों के साथ अधिकांश सांख्यिक कार्यों को वेक्टर किया जा सकता है - इसका मतलब है कि वे पारंपरिक पुनरावृत्ति की तुलना में बहुत तेज हैं। OTOH, कुछ ऑपरेशन (जैसे स्ट्रिंग और regex) स्वाभाविक रूप से वेक्टर के लिए कठिन हैं। इस मामले में, यह समझना महत्वपूर्ण है कि आपके डेटा पर कैसे लूप किया जाए। आपके डेटा पर कब और कैसे लूपिंग किया जाना है, इस बारे में अधिक जानकारी के लिए कृपया पंडों के साथ लूप्स पढ़ें - मुझे कब ध्यान देना चाहिए?
CS95

@coldspeed: मुख्य मुद्दा यह नहीं चुन रहा था कि कई विकल्पों में से उच्च-प्रदर्शन कौन सा था, यह पैंडस सिंटैक्स से लड़ रहा था ताकि यह काम कर सके, वापस v0.11.0 के आसपास ।
11

वास्तव में, टिप्पणी भविष्य के पाठकों के लिए है जो पुनरावृत्त समाधानों की तलाश कर रहे हैं, जो या तो किसी भी बेहतर को नहीं जानते हैं, या जो जानते हैं कि वे क्या कर रहे हैं।
CS95

जवाबों:


109

उपयोगकर्ता 1827356 के उत्तर का निर्माण, आप एक पास का उपयोग करके असाइनमेंट कर सकते हैं 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: कृपया विशाल मेमोरी खपत और कम गति के बारे में पता करें: https://ys-l.github.io/posts/2015/08/28/how-not-to-use-pandas-apply/ !


2
बस जिज्ञासा से बाहर, क्या ऐसा करने से बहुत सारी मेमोरी का उपयोग करने की उम्मीद है? मैं एक डेटाफ्रेम पर कर रहा हूं जो 2.5mil पंक्तियों को रखता है, और मैं लगभग मेमोरी समस्याओं में भाग गया (यह भी सिर्फ 1 कॉलम लौटने से बहुत धीमा है)।
जेफरी ०४

2
'df.join (df.textcol.apply (lambda s: pd.Series) ({' feature1 ': s + 1,' feature2 ': s-1}))' 'मेरे विचार से एक बेहतर विकल्प होगा।
शिवम के। ठक्कर

@ शिवभक्तकार आपको क्यों लगता है कि आपका सुझाव एक बेहतर विकल्प होगा? क्या यह अधिक कुशल होगा जिसे आप सोचते हैं या स्मृति की लागत कम है?
tsando

1
कृपया गति और आवश्यक मेमोरी पर विचार करें: ys-l.github.io/posts/2015/08/28/how-not-to-use-pandas-apply
Make42

190

मैं आमतौर पर इसका उपयोग करता हूं 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
लेकिन आप क्या करते हैं यदि आपके पास 6 के बजाय 50 कॉलम इस तरह जोड़े जाते हैं?
अधिकतम

14
@ मोमtemp = list(zip(*df['num'].map(powers))); for i, c in enumerate(columns): df[c] = temp[c]
ओस्ट्रोकैच

8
@ostrokach मुझे लगता है कि आपका मतलब है for i, c in enumerate(columns): df[c] = temp[i]। इसके लिए धन्यवाद, मुझे वास्तव में इसका उद्देश्य मिला enumerate: D
rokarvaj

4
यह अब तक का सबसे सुरुचिपूर्ण और पठनीय समाधान है जिसके लिए मैं आया हूं। जब तक आपको प्रदर्शन की समस्याएं नहीं हो रही हैं, मुहावरा zip(*df['col'].map(function))संभवतः जाने का रास्ता है।
फ्राँस्वा लेब्लांक


84

यह मैंने अतीत में किया है

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

सम्पूर्णता के लिए संपादन

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

नए डेटा को मूल डेटाफ़्रेम से जोड़ने के लिए कॉन्ट्रास () मर्ज की तुलना में सरल () दिखता है।
जीरा

2
अच्छा जवाब, अगर आपको आवेदन के बाहर कॉलम निर्दिष्ट करना है, तो आपको एक तानाशाह या मर्ज का उपयोग करने की आवश्यकता नहीं हैdf[['col1', 'col2']] = df['col3'].apply(lambda x: pd.Series('val1', 'val2'))
मैट

66

95% उपयोग मामलों के लिए इसे पूरा करने का सही और आसान तरीका है:

>>> 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

क्या आपको यह नहीं लिखना चाहिए: df = df.apply (उदाहरण (df), धुरी = 1) मुझे सही करें अगर मैं गलत हूं, तो मैं सिर्फ एक नौसिखिया
हूं

1
@ user299791, नहीं, इस मामले में आप एक प्रथम श्रेणी की वस्तु के रूप में उदाहरण पेश कर रहे हैं, इसलिए आप स्वयं ही कार्य कर रहे हैं। यह फ़ंक्शन प्रत्येक पंक्ति पर लागू होगा।
माइकल डेविड वॉटसन

हाय माइकल, आपके जवाब ने मेरी समस्या में मदद की। निश्चित रूप से आपका समाधान मूल पांडा की df.assign () विधि से बेहतर है, क्यूज़ यह प्रति कॉलम एक बार है। असाइन () का उपयोग करते हुए, यदि आप 2 नए कॉलम बनाना चाहते हैं, तो आपको नए कॉलम 1 प्राप्त करने के लिए df1 पर काम करने के लिए df1 का उपयोग करना होगा, फिर दूसरे नए कॉलम को बनाने के लिए df1 पर काम करने के लिए df2 का उपयोग करें ... यह काफी नीरस है। लेकिन आपके तरीके ने मेरी जान बचाई !!! धन्यवाद!!!
कॉमेंटलेज़-वौस

1
क्या प्रति पंक्ति एक बार कॉलम असाइनमेंट कोड नहीं चलेगा? क्या pd.Series({k:v})एवन के उत्तर की तरह कॉलम असाइनमेंट को वापस करना और उसे क्रमबद्ध करना बेहतर नहीं होगा ?
डेनिस डे बर्नार्डी

30

2018 में, मैं apply()तर्क के साथ उपयोग करता हूंresult_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
यह है कि आप इसे कैसे करते हैं, आजकल!
Make42

1
इसने 2020 में बॉक्स से बाहर काम किया जबकि कई अन्य सवाल नहीं किए। इसके अलावा यह उपयोग नहीं करता है pd.Series जो प्रदर्शन के मुद्दों के बारे में हमेशा अच्छा है
थियो रूबेनैच

1
यह एक अच्छा उपाय है। एकमात्र समस्या यह है, आप 2 नए जोड़े गए कॉलम के लिए नाम नहीं चुन सकते। आपको बाद में df.rename (कॉलम = {0: 'col1', 1: 'col2'}) करने की आवश्यकता है
pedram bashiri

2
@pedrambashiri यदि आप जिस फ़ंक्शन को df.applyरिटर्न करने के लिए पास करते हैं dict, तो कुंजी के अनुसार कॉलम का नाम आ जाएगा।
एसबी

24

महज प्रयोग करें 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
यह इंगित करने में मदद करता है कि विकल्प 0.23 में नया है । प्रश्न 0.11 पर वापस पूछा गया था
smci

अच्छा, यह सरल है और अभी भी बड़े करीने से काम करता है। यह वही है जिसकी मुझे तलाश थी। धन्यवाद
इसहाक सिम

एक पहले वाले उत्तर
tar

22

सारांश: यदि आप केवल कुछ कॉलम बनाना चाहते हैं, तो उपयोग करेंdf[['new_col1','new_col2']] = df[['data1','data2']].apply( function_of_your_choosing(x), axis=1)

इस समाधान के लिए, आपके द्वारा बनाए जा रहे नए स्तंभों की संख्या .apply () फ़ंक्शन के इनपुट के रूप में आपके द्वारा उपयोग किए जाने वाले संख्या स्तंभों के बराबर होनी चाहिए। यदि आप कुछ और करना चाहते हैं, तो अन्य उत्तरों पर एक नज़र डालें।

विवरण मान लें कि आपके पास दो-कॉलम डेटाफ़्रेम है। पहला कॉलम एक व्यक्ति की ऊंचाई है जब वे 10 साल के होते हैं; जब वे 20 वर्ष के होते हैं तो दूसरे को उनकी ऊंचाई कहा जाता है।

मान लें कि आपको प्रत्येक व्यक्ति की ऊंचाइयों और प्रत्येक व्यक्ति की ऊंचाइयों के योग दोनों की गणना करने की आवश्यकता है। प्रत्येक पंक्ति में दो मान हैं।

आप इसे निम्न, जल्द ही लागू होने वाले फ़ंक्शन के माध्यम से कर सकते हैं:

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]

आप इस फ़ंक्शन का उपयोग इस तरह कर सकते हैं:

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

(स्पष्ट होने के लिए: यह लागू फ़ंक्शन प्रत्येक डेटा से सबसेट किए गए डेटाफ़्रेम में मान लेता है और एक सूची देता है।)

हालाँकि, यदि आप ऐसा करते हैं:

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

आप 1 नया कॉलम बनाएंगे, जिसमें [माध्य, योग] सूचियाँ हैं, जिन्हें आप संभवतः बचना चाहते हैं, क्योंकि इसके लिए एक और लैम्बडा / अप्लाई की आवश्यकता होगी।

इसके बजाय, आप प्रत्येक मान को उसके कॉलम में तोड़ना चाहते हैं। ऐसा करने के लिए, आप एक साथ दो कॉलम बना सकते हैं:

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

4
पांडा 0.23 के लिए, आपको सिंटैक्स का उपयोग करना होगा:df["mean"], df["sum"] = df[['height_at_age_10','height_at_age_20']] .apply(mean_and_sum(x),axis=1)
समरएला

यह फ़ंक्शन त्रुटि बढ़ा सकता है। वापसी समारोह होना चाहिए return pd.Series([mean,sum])
कनिष्क मेरा

22

मेरे लिए यह काम किया:

इनपुट df

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

समारोह

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

2 नए कॉलम बनाएँ:

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

आउटपुट:

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

13

मैंने ऐसा करने के कई तरीके देखे हैं और यहाँ दिखाया गया तरीका (पंडों की श्रंखला को लौटाना) सबसे अधिक कारगर नहीं लगता।

यदि हम यादृच्छिक डेटा की लार्जिश डेटाफ़्रेम के साथ शुरुआत करते हैं:

# 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'

यहाँ दिखाया गया उदाहरण:

# 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 लूप, सर्वश्रेष्ठ 3: 2.77 प्रति लूप

एक वैकल्पिक विधि:

# 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 लूप, सर्वश्रेष्ठ 3: 8.85 एमएस प्रति लूप

मेरे द्वारा यह बहुत अधिक कुशल है कि टुपल्स की एक श्रृंखला लेना और फिर उसे एक DataFrame में बदलना। मुझे लोगों की सोच सुनने में दिलचस्पी होगी, हालांकि अगर मेरे काम करने में कोई त्रुटि है।


यह वास्तव में उपयोगी है! मुझे फंक्शन रिटर्निंग सीरीज़ के तरीकों की तुलना में 30 गुना स्पीड-अप मिला।
पुष्कर निमाकर

9

स्वीकृत समाधान बहुत सारे डेटा के लिए बहुत धीमा होने वाला है। सबसे बड़ी संख्या में उत्थान के साथ समाधान पढ़ना थोड़ा मुश्किल है और संख्यात्मक डेटा के साथ भी धीमा है। यदि प्रत्येक नए स्तंभ की गणना दूसरों से स्वतंत्र रूप से की जा सकती है, तो मैं उनमें से प्रत्येक को बिना उपयोग किए सीधे असाइन करूंगाapply

नकली चरित्र डेटा के साथ उदाहरण

एक DataFrame में 100,000 स्ट्रिंग्स बनाएँ

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

मान लीजिए कि हम मूल प्रश्न में किए गए कुछ पाठ सुविधाओं को निकालना चाहते हैं। उदाहरण के लिए, चलो पहले वर्ण को निकालते हैं, अक्षर 'e' की घटना को गिनते हैं और वाक्यांश को कैपिटलाइज़ करते हैं।

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

समय

%%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)

आश्चर्यजनक रूप से, आप प्रत्येक मूल्य के माध्यम से लूप करके बेहतर प्रदर्शन प्राप्त कर सकते हैं

%%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)

नकली संख्यात्मक डेटा के साथ एक और उदाहरण

1 मिलियन यादृच्छिक संख्या बनाएं और powersऊपर से फ़ंक्शन का परीक्षण करें ।

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)

प्रत्येक स्तंभ को असाइन करना 25x तेज और बहुत पठनीय है:

%%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)

मैंने यहां अधिक विवरण के साथ इसी तरह की प्रतिक्रिया दी है कि applyआमतौर पर जाने का रास्ता क्यों नहीं है।


8

दो अन्य समान प्रश्नों में एक ही उत्तर पोस्ट किया है। जिस तरह से मैं ऐसा करना पसंद करता हूं वह एक श्रृंखला में फ़ंक्शन के रिटर्न मान को लपेटने के लिए है:

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

और फिर अलग कॉलम बनाने के लिए निम्नानुसार उपयोग लागू करें:

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

1

आप मानों के बजाय पूरी पंक्ति वापस कर सकते हैं:

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

जहाँ फ़ंक्शन पंक्ति को लौटाता है

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

नहीं, मैं extract_text_featuresdf के हर स्तंभ पर लागू नहीं होना चाहता , केवल पाठ कॉलम परdf.textcol
jci

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

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

इसने मेरे लिए काम किया। नया कॉलम संसाधित पुराने कॉलम डेटा के साथ बनाया जाएगा।


2
यह 'कई नए कॉलम' नहीं लौटाता
पेडराम बशीरी

यह 'कई नए कॉलम' वापस नहीं करता है, इसलिए यह सवाल का जवाब नहीं देता है। क्या आप कृपया इसे हटा सकते हैं?
एसएमसीआई
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.