Разделить (взорвать) запись строки фрейма данных pandas на отдельные строки
у меня есть pandas dataframe в котором один столбец текстовых строк содержит значения, разделенные запятыми. Я хочу разделить каждое поле CSV и создать новую строку для каждой записи (предположим, что 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 метод, кажется, принимает только одну строку в качестве возвращаемого значения, когда он используется на оси, и я не могу получить .transform на работу. Любые предложения будут очень ценится!
пример:
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}])
Я знаю, что это не сработает, потому что мы теряем метаданные DataFrame, проходя через numpy, но это должно дать вам представление о том, что я пытался сделать:
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)
13 ответов:
Как насчет чего-то вроде этого:
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тогда вам просто нужно переименовать столбцы
после болезненных экспериментов, чтобы найти что-то быстрее, чем принятый ответ, я получил эту работу. Он работал примерно в 100 раз быстрее на наборе данных, который я пробовал.
Если кто-то знает способ сделать это более элегантным, пожалуйста, измените мой код. Я не мог найти способ, который работает без установки других столбцов, которые вы хотите сохранить в качестве индекса, а затем сбросить индекс и переименовать столбцы, но я бы предположил, что есть что-то еще, что работает.
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: более общей векторной функции, которая будет работать для нескольких
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, lens) 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, lens) 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]Demo:
несколько
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настройка:
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
обновление:общий векторизованный подход (будет работать также для нескольких столбцов):
Оригинал DF:
In [177]: df Out[177]: var1 var2 var3 0 a,b,c 1 XX 1 d,e,f,x,y 2 ZZустранение:
сначала давайте преобразуем строки CSV в списки:
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
ответ:
вдохновленный @ AFinkelstein solution, я хотел сделать его немного более обобщенным, который можно было бы применить к DF с более чем двумя столбцами и так же быстро, ну почти так же быстро, как решение Афинкельштейна):
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С этой функцией,исходный вопрос так же просто, как:
tidy_split(a, 'var1', sep=',')
подобный вопрос так: панды: как разделить текст в столбце на несколько строк?
вы могли бы сделать:
>> a=pd.DataFrame({"var1":"a,b,c d,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
Я придумал решение для фреймов данных с произвольным числом столбцов (в то же время разделяя только записи одного столбца за раз).
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
вот довольно простое сообщение, которое использует
splitметод от пандstraccessor, а затем использует 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
TL; DR
import pandas as pd import numpy as np def explode_str(df, col, sep): s = df[col] i = np.arange(len(s)).repeat(s.str.count(sep) + 1) return df.iloc[i].assign(**{col: sep.join(s).split(sep)}) def explode_list(df, col): s = df[col] i = np.arange(len(s)).repeat(s.str.len()) return df.iloc[i].assign(**{col: np.concatenate(s)})
демонстрация
explode_str(a, 'var1', ',') var1 var2 0 a 1 0 b 1 0 c 1 1 d 2 1 e 2 1 f 2давайте создадим новый фрейм данных
dчто есть спискиd = a.assign(var1=lambda d: d.var1.str.split(',')) explode_list(d, 'var1') var1 var2 0 a 1 0 b 1 0 c 1 1 d 2 1 e 2 1 f 2
Общие Замечания
я буду использовать
np.arangeСrepeatдля создания позиций индекса фрейма данных, которые я могу использовать сiloc.часто задаваемые вопросы
почему бы мне не использовать
loc?потому что индекс не может быть уникальным и использовать
locвернет каждую строку, которая соответствует запрошенному индексу.почему бы вам не использовать
valuesатрибут и разрезать?при вызове
values, если весь фрейм данных находится в одном Связном "блоке", Pandas вернет представление массива, который является "блоком". В противном случае пандам придется сколотить новый массив. При cobbling этот массив должен иметь единый тип dtype. Часто это означает возврат массива с dtype, который являетсяobject. С помощьюilocвместо нарезкиvaluesатрибут, я облегчаю себя от того, чтобы иметь дело с этим.почему вы используете
assign?когда я использую
assignиспользуя то же имя столбца, которое я взрываю, я перезаписываю существующий столбец и сохраняю его позицию в фрейме данных.почему значения Индекса повторить?
в силу использования
ilocна повторяющихся позициях результирующий индекс показывает тот же повторяющийся шаблон. Один повтор для каждого элемента списка или строка.
Это можно сбросить с помощьюreset_index(drop=True)
Для Строк
я не хочу, чтобы разделить строки преждевременно. Поэтому вместо этого я считаю вхождения
яsepаргумент, предполагающий, что если бы я разделил, длина результирующего списка была бы на один больше, чем количество разделителей.sepдоjoinстрокиsplit.def explode_str(df, col, sep): s = df[col] i = np.arange(len(s)).repeat(s.str.count(sep) + 1) return df.iloc[i].assign(**{col: sep.join(s).split(sep)})Списки
похожие как для строк, за исключением того, что мне не нужно подсчитывать вхождения
sepпотому что его уже разбить.я использую библиотеки numpy это
concatenateчтобы сжать списки вместе.import pandas as pd import numpy as np def explode_list(df, col): s = df[col] i = np.arange(len(s)).repeat(s.str.len()) return df.iloc[i].assign(**{col: np.concatenate(s)})
на основе превосходного @Dmulligan's решение, вот общая векторизованная функция (без циклов), которая разбивает столбец фрейма данных на несколько строк и объединяет его обратно в исходный фрейм данных. Он также использует большой общий
просто использовал отличный ответ 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
строковая функция split может принимать параметр логический аргумент 'expand'.
вот решение с использованием этого аргумента:
a.var1.str.split(",",expand=True).set_index(a.var2).stack().reset_index(level=1, drop=True).reset_index().rename(columns={0:"var1"})
Я придумал следующее решение этой проблемы:
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
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)
Comments