拆分(爆炸)pandas数据框string条目分隔行
我有一个pandas dataframe
,其中一列文本string包含逗号分隔值。 我想分割每个CSV字段,并为每个条目创build一个新的行(假设CSV是干净的,只需要拆分',')。 比如a
应该变成b
:
In [7]: a Out[7]: var1 var2 0 a,b,c 1 1 d,e,f 2 In [8]: b Out[8]: var1 var2 0 a 1 1 b 1 2 c 1 3 d 2 4 e 2 5 f 2
到目前为止,我已经尝试了各种简单的函数,但是.apply
方法在.apply
使用时似乎只接受一行作为返回值,并且我无法使用.transform
来工作。 我们欢迎所有的build议!
示例数据:
from pandas import DataFrame import numpy as np a = DataFrame([{'var1': 'a,b,c', 'var2': 1}, {'var1': 'd,e,f', 'var2': 2}]) b = DataFrame([{'var1': 'a', 'var2': 1}, {'var1': 'b', 'var2': 1}, {'var1': 'c', 'var2': 1}, {'var1': 'd', 'var2': 2}, {'var1': 'e', 'var2': 2}, {'var1': 'f', 'var2': 2}])
我知道这是行不通的,因为我们通过numpy失去了DataFrame的元数据,但它应该让你知道我想要做什么:
def fun(row): letters = row['var1'] letters = letters.split(',') out = np.array([row] * len(letters)) out['var1'] = letters a['idx'] = range(a.shape[0]) z = a.groupby('idx') z.transform(fun)
怎么样这样的事情:
In [55]: pd.concat([Series(row['var2'], row['var1'].split(',')) for _, row in a.iterrows()]).reset_index() Out[55]: index 0 0 a 1 1 b 1 2 c 1 3 d 2 4 e 2 5 f 2
那么你只需要重命名列
经过痛苦的实验find比接受的答案更快的东西,我得到了这个工作。 它在我尝试过的数据集上运行速度快了100倍。
如果有人知道一个办法,使这个更优雅,一定要修改我的代码。 我无法find一种方法,没有设置其他列,你想保留作为索引,然后重置索引和重命名的列,但我想像还有其他的工作。
b = DataFrame(a.var1.str.split(',').tolist(), index=a.var2).stack() b = b.reset_index()[[0, 'var2']] # var1 variable is currently labeled 0 b.columns = ['var1', 'var2'] # renaming var1
更新2:更通用的vector化函数,这将适用于多个normal
和多list
列
def explode(df, lst_cols, fill_value=''): # make sure `lst_cols` is a list if lst_cols and not isinstance(lst_cols, list): lst_cols = [lst_cols] # all columns except `lst_cols` idx_cols = df.columns.difference(lst_cols) # calculate lengths of lists lens = df[lst_cols[0]].str.len() if (lens > 0).all(): # ALL lists in cells aren't empty return pd.DataFrame({ col:np.repeat(df[col].values, df[lst_cols[0]].str.len()) for col in idx_cols }).assign(**{col:np.concatenate(df[col].values) for col in lst_cols}) \ .loc[:, df.columns] else: # at least one list in cells is empty return pd.DataFrame({ col:np.repeat(df[col].values, df[lst_cols[0]].str.len()) for col in idx_cols }).assign(**{col:np.concatenate(df[col].values) for col in lst_cols}) \ .append(df.loc[lens==0, idx_cols]).fillna(fill_value) \ .loc[:, df.columns]
演示:
多list
列 – 所有list
列在每行中必须具有相同的元素数量:
In [36]: df Out[36]: aaa myid num text 0 10 1 [1, 2, 3] [aa, bb, cc] 1 11 2 [1, 2] [cc, dd] 2 12 3 [] [] 3 13 4 [] [] In [37]: explode(df, ['num','text'], fill_value='') Out[37]: aaa myid num text 0 10 1 1 aa 1 10 1 2 bb 2 10 1 3 cc 3 11 2 1 cc 4 11 2 2 dd 2 12 3 3 13 4
build立:
df = pd.DataFrame({ 'aaa': {0: 10, 1: 11, 2: 12, 3: 13}, 'myid': {0: 1, 1: 2, 2: 3, 3: 4}, 'num': {0: [1, 2, 3], 1: [1, 2], 2: [], 3: []}, 'text': {0: ['aa', 'bb', 'cc'], 1: ['cc', 'dd'], 2: [], 3: []} })
CSV列:
In [46]: df Out[46]: var1 var2 var3 0 a,b,c 1 XX 1 d,e,f,x,y 2 ZZ In [47]: explode(df.assign(var1=df.var1.str.split(',')), 'var1') Out[47]: var1 var2 var3 0 a 1 XX 1 b 1 XX 2 c 1 XX 3 d 2 ZZ 4 e 2 ZZ 5 f 2 ZZ 6 x 2 ZZ 7 y 2 ZZ
使用这个小技巧,我们可以将CSV类列转换为list
列:
In [48]: df.assign(var1=df.var1.str.split(',')) Out[48]: var1 var2 var3 0 [a, b, c] 1 XX 1 [d, e, f, x, y] 2 ZZ
更新: 通用vector化方法(也将工作的多列):
原始DF:
In [177]: df Out[177]: var1 var2 var3 0 a,b,c 1 XX 1 d,e,f,x,y 2 ZZ
解:
首先让我们将CSVstring转换为列表:
In [178]: lst_col = 'var1' In [179]: x = df.assign(**{lst_col:df[lst_col].str.split(',')}) In [180]: x Out[180]: var1 var2 var3 0 [a, b, c] 1 XX 1 [d, e, f, x, y] 2 ZZ
现在我们可以做到这一点:
In [181]: pd.DataFrame({ ...: col:np.repeat(x[col].values, x[lst_col].str.len()) ...: for col in x.columns.difference([lst_col]) ...: }).assign(**{lst_col:np.concatenate(x[lst_col].values)})[x.columns.tolist()] ...: Out[181]: var1 var2 var3 0 a 1 XX 1 b 1 XX 2 c 1 XX 3 d 2 ZZ 4 e 2 ZZ 5 f 2 ZZ 6 x 2 ZZ 7 y 2 ZZ
OLD回答:
受到@AFinkelstein解决scheme的启发,我想使它更具概括性,可以应用于DF数量超过两列,速度也几乎与AFinkelstein解决scheme一样快:
In [2]: df = pd.DataFrame( ...: [{'var1': 'a,b,c', 'var2': 1, 'var3': 'XX'}, ...: {'var1': 'd,e,f,x,y', 'var2': 2, 'var3': 'ZZ'}] ...: ) In [3]: df Out[3]: var1 var2 var3 0 a,b,c 1 XX 1 d,e,f,x,y 2 ZZ In [4]: (df.set_index(df.columns.drop('var1',1).tolist()) ...: .var1.str.split(',', expand=True) ...: .stack() ...: .reset_index() ...: .rename(columns={0:'var1'}) ...: .loc[:, df.columns] ...: ) Out[4]: var1 var2 var3 0 a 1 XX 1 b 1 XX 2 c 1 XX 3 d 2 ZZ 4 e 2 ZZ 5 f 2 ZZ 6 x 2 ZZ 7 y 2 ZZ
这是我为这个常见任务写的一个函数 。 它比Series
/ stack
方法更有效率。 列顺序和名称保留。
def tidy_split(df, column, sep='|', keep=False): """ Split the values of a column and expand so the new DataFrame has one split value per row. Filters rows where the column is missing. Params ------ df : pandas.DataFrame dataframe with the column to split and expand column : str the column to split and expand sep : str the string used to split the column's values keep : bool whether to retain the presplit value as it's own row Returns ------- pandas.DataFrame Returns a dataframe with the same columns as `df`. """ indexes = list() new_values = list() df = df.dropna(subset=[column]) for i, presplit in enumerate(df[column].astype(str)): values = presplit.split(sep) if keep and len(values) > 1: indexes.append(i) new_values.append(presplit) for value in values: indexes.append(i) new_values.append(value) new_df = df.iloc[indexes, :].copy() new_df[column] = new_values return new_df
有了这个function, 原来的问题就像这样简单:
tidy_split(a, 'var1', sep=',')
类似的问题如下: pandas:如何将一列中的文本分成多行?
你可以这样做:
>> a=pd.DataFrame({"var1":"a,b,cd,e,f".split(),"var2":[1,2]}) >> s = a.var1.str.split(",").apply(pd.Series, 1).stack() >> s.index = s.index.droplevel(-1) >> del a['var1'] >> a.join(s) var2 var1 0 1 a 0 1 b 0 1 c 1 2 d 1 2 e 1 2 f
我想出了一个具有任意数量列的数据框的解决scheme(同时仍然只分离一列的条目)。
def splitDataFrameList(df,target_column,separator): ''' df = dataframe to split, target_column = the column containing the values to split separator = the symbol used to perform the split returns: a dataframe with each entry for the target column separated, with each element moved into a new row. The values in the other columns are duplicated across the newly divided rows. ''' def splitListToRows(row,row_accumulator,target_column,separator): split_row = row[target_column].split(separator) for s in split_row: new_row = row.to_dict() new_row[target_column] = s row_accumulator.append(new_row) new_rows = [] df.apply(splitListToRows,axis=1,args = (new_rows,target_column,separator)) new_df = pandas.DataFrame(new_rows) return new_df
刚刚使用jiln从上面的优秀答案,但需要扩大到拆分多个列。 以为我会分享。
def splitDataFrameList(df,target_column,separator): ''' df = dataframe to split, target_column = the column containing the values to split separator = the symbol used to perform the split returns: a dataframe with each entry for the target column separated, with each element moved into a new row. The values in the other columns are duplicated across the newly divided rows. ''' def splitListToRows(row, row_accumulator, target_columns, separator): split_rows = [] for target_column in target_columns: split_rows.append(row[target_column].split(separator)) # Seperate for multiple columns for i in range(len(split_rows[0])): new_row = row.to_dict() for j in range(len(split_rows)): new_row[target_columns[j]] = split_rows[j][i] row_accumulator.append(new_row) new_rows = [] df.apply(splitListToRows,axis=1,args = (new_rows,target_column,separator)) new_df = pd.DataFrame(new_rows) return new_df
我已经想出了以下解决scheme来解决这个问题:
def iter_var1(d): for _, row in d.iterrows(): for v in row["var1"].split(","): yield (v, row["var2"]) new_a = DataFrame.from_records([i for i in iter_var1(a)], columns=["var1", "var2"])
另一个使用python copy package的解决scheme
import copy new_observations = list() def pandas_explode(df, column_to_explode): new_observations = list() for row in df.to_dict(orient='records'): explode_values = row[column_to_explode] del row[column_to_explode] if type(explode_values) is list or type(explode_values) is tuple: for explode_value in explode_values: new_observation = copy.deepcopy(row) new_observation[column_to_explode] = explode_value new_observations.append(new_observation) else: new_observation = copy.deepcopy(row) new_observation[column_to_explode] = explode_values new_observations.append(new_observation) return_df = pd.DataFrame(new_observations) return return_df df = pandas_explode(df, column_name)
这是一个非常简单的消息,它使用pandas str
访问器中的split
方法,然后使用NumPy将每行压扁成单个数组。
通过用np.repeat
重复非分割列正确的次数来检索相应的值。
var1 = df.var1.str.split(',', expand=True).values.ravel() var2 = np.repeat(df.var2.values, len(var1) / len(df)) pd.DataFrame({'var1': var1, 'var2': var2}) var1 var2 0 a 1 1 b 1 2 c 1 3 d 2 4 e 2 5 f 2