2016-12-02 53 views
0

道歉凌亂標題:問題如下:使用Python,Pandas和Apply/Lambda,我該如何編寫一個創建多個新列的函數?

我有形式的一些數據幀:

df1 = 
    Entries 
0 "A Level" 
1 "GCSE" 
2 "BSC" 

我也有以下形式的數據幀:

df2 = 
    Secondary Undergrad 
0 "A Level" "BSC" 
1 "GCSE"  "BA" 
2 "AS Level" "MSc" 

我有一個函數用於搜索df1中的每個條目,查找df2每列中的單詞。匹配的,保存的話(Words_Present):

def word_search(df,group,words): 
Yes, No = 0,0 
Words_Present = []  
    for i in words:  
    match_object = re.search(i,df) 
    if match_object: 
     Words_Present.append(i) 
     Yes = 1  
    else: 
     No = 0 
if Yes == 1: 
    Attribute = 1 

return Attribute 

我應用此功能在在DF1所有條目,並在DF2所有列,使用下面的迭代:

for i in df2: 

    terms = df2[i].values.tolist() 

    df1[i] = df1['course'][0:1].apply(lambda x: word_search(x,i,terms)) 

這就產生一個輸出DF它看起來像:

df1 = 
    Entries Secondary  undergrad 
0 "A Level"  1    0 
1 "GCSE"  1    0 
2 "AS Level" 1    0 

我想修改Word_Search功能所涉及的Words_Present列表輸出以及屬性,並輸入到這些新列,所以是m Ÿ最終DF1陣列的樣子:

期望中的數據框:

Entries  Secondary Words Found   undergrad Words Found 
0 "A Level"  1   "A Level"    0 
1 "GCSE"  1   "GCSE"     0 
2 "AS Level" 1   "AS Level"    0 

如果我做的:

def word_search(df,group,words): 
Yes, No = 0,0 
Words_Present = []  
    for i in words:  
    match_object = re.search(i,df) 
    if match_object: 
     Words_Present.append(i) 
     Yes = 1  
    else: 
     No = 0 
if Yes == 1: 
    Attribute = 1 
if Yes == 0: 
    Attribute = 0 

return Attribute,Words_Present 

我的功能,因此現在有多個輸出。因此,應用以下:

for i in df2: 

    terms = df2[i].values.tolist() 

    df1[i] = df1['course'][0:1].apply(lambda x: word_search(x,i,terms)) 

我的輸出如下:

Entries  Secondary     undergrad 
0 "A Level"  [1,"A Level"]    0 
1 "GCSE"  [1, "GCSE"]     0 
2 "AS Level" [1, "AS Level"]    0 

pd.apply()的輸出始終是一個熊貓系列,所以它只是猛推到一切DF的單細胞[我]其中我=次要的。

是否可以將.apply的輸出分成兩個單獨的列,如所需的數據框所示?

我諮詢了很多問題,但似乎都不直接產生多列時,包含在應用語句中的函數來處理具有多種輸出:

Applying function with multiple arguments to create a new pandas column

Create multiple columns in Pandas Dataframe from one function

Apply pandas function to column to create multiple new columns?

例如,我也嘗試過:

for i in df2: 

    terms = df2[i].values.tolist() 

    [df1[i],df1[i]+"Present"] = pd.concat([df1['course'][0:1].apply(lambda x: word_search(x,i,terms))]) 

但這只是產生錯誤,如:

raise ValueError('Length of values does not match length of ' 'index') 

有沒有辦法使用應用的方式,但仍然直接提取的額外信息爲多列?

非常感謝,歉意的長度。

+1

我已經回答了你的問題,但是我想對'df1 ['course'] [0:1]''這樣的表達做一個側面評論':這被稱爲鏈接索引,這在熊貓中應該避免,因爲它很慢,並且當您想要更改數據框中的元素時常常會導致問題。相反,'df1.loc [0:1,'course']'是引用同一事物的正確方法。 –

+1

(另外,如果沒有找到匹配的單詞,你的'word_search()'函數會返回一個錯誤,因爲'Attribute'除非'Yes == 1!'纔會被分配!) –

+0

你的第一點:謝謝,我不知道,並且正在根據我繼承的文件編寫此代碼。 您的第二點:我在複製我的代碼時犯了一個錯誤:我打算在if語句的兩個部分中包含這兩個代碼。我急於想到這一點,我錯過了它。 謝謝你指出。 [兩者現在都更新] – Chuck

回答

1

直接回答你的問題是:使用DataFrame對象的應用方法,所以你會做df1.apply()

但是,對於這個問題,以及一般熊貓的任何事情,都應該嘗試引導而不是遍歷行 - 它更快更清晰。

它看起來像你試圖將Entries分爲SecondaryUndergrad,並保存用於進行匹配的關鍵字。如果假設的Entries每個元素都沒有超過一個關鍵字匹配(即你不會碰上「GCSE的A Level」),你可以做到以下幾點:

df = df1.copy() 
df['secondary_words_found'] = df.Entries.str.extract('(A Level|GCSE|AS Level)') 
df['undergrad_words_found'] = df.Entries.str.extract('(BSC|BA|MSc)') 
df['secondary'] = df.secondary_words_found.notnull() * 1 
df['undergrad'] = df.undergrad_words_found.notnull() * 1 

編輯: 針對您的問題與更多的類別和關鍵字,你可以繼續本解決方案的精神,通過使用合適的for循環,並在提取方法中做'(' + '|'.join(df2['Undergrad'].values) + ')'

但是,如果你有精確匹配,可以通過樞軸的組合做的一切,並加入:

keywords = df2.stack().to_frame('Entries').reset_index().drop('level_0', axis = 1).rename(columns={'level_1':'category'}) 
df = df1.merge(keywords, how = 'left') 
for colname in df.category: 
    df[colname] = (df.Entries == colname) * 1 # Your indicator variable 
    df.loc[df.category == colname, colname + '_words_found'] = df.loc[df.category == colname, 'Entries'] 

第一行「支點」關鍵字的表到關鍵字2列數據幀和類別。您的關鍵字列必須與df1中的列相同;在SQL中,這將被稱爲您要加入這些表的外鍵。

此外,您通常希望避免重複索引或列,在您的情況下,在所需的數據幀中爲Words Found

爲了完整起見,如果你堅持使用apply方法,你會遍歷DataFrame的每一行;您的代碼會是這個樣子:

secondary_words = df2.Secondary.values 
undergrad_words = df2.Undergrad.values 
def(s): 
    if s.Entries.isin(secondary_words): 
     return pd.Series({'Entries':s.Entries, 'Secondary':1, 'secondary_words_found':s.Entries, 'Undergrad':0, 'undergrad_words_found':''}) 
    elif s.Entries.isin(undergrad_words): 
     return pd.Series({'Entries':s.Entries, 'Secondary':0, 'secondary_words_found':'', 'Undergrad':1, 'undergrad_words_found':s.Entries}) 
    else: 
     return pd.Series({'Entries':s.Entries, 'Secondary':0, 'secondary_words_found':'', 'Undergrad':0, 'undergrad_words_found':''}) 

這第二個版本將只在你想讓它如果Entries元素是完全一樣的df2其對應單元的情況下工作。你當然不希望這樣做,因爲它更加混亂,而且如果你有很多數據需要處理,速度會明顯變慢。

+0

肯你推導出「看起來你試圖將入學分爲中學或本科」是正確的。這完全正確。然而,類別的數量要長得多(中學,本科,Postgrad等),對於每個類別的關聯詞彙更是如此。這是我使用for循環的原因。實際上可能存在註冊多個關鍵字的情況 - 我想將所有這些單詞保存在df1中每個條目的字符串列表中。這會如何改變你所寫的內容? **非常感謝您的幫助:)** – Chuck

+0

實際上,有很多條目,我將不得不自動執行,因爲我沒有時間寫出...... str.extract('( A Level | GCSE | AS Level)')'用於每個類別中的每個文本。儘管如此,我應該能夠使這個更加自動化,並且包含一些Re模塊來完成這項工作? – Chuck

+1

你期望得到完全匹配嗎?即條目完全等於'A級'或'GCSE'等,所以像'GCSE-O'這樣的情況就不會有匹配(假設'GCSE-O'不在你的名字表中)?在那種情況下,有一種更乾淨的做事方式。 –

相關問題