Цель Кодекса:
Цель функции, над которой я работаю, — взять определенный столбец из DataFrame, который содержит несколько <tag><content>
пары в виде строки и эффективно разверните их в отдельные столбцы.
Пример файла можно найти здесь (около 3 млн записей), который можно загрузить как DataFrame с помощью:
def load_refseq_coordinates(ifile):
# https://m.ensembl.org/info/website/upload/gff3.html
return pd.read_csv(ifile, sep='t', comment="https://codereview.stackexchange.com/questions/265561/#",
names=['seqid', 'source', 'type', 'start', 'end', 'score',
'strand', 'phase', 'attributes'])
return df
Загруженный DataFrame будет иметь столбец с именем attributes
с таким содержанием, как:
атрибуты |
---|
ID = NC_000001.11: 1..248956422; Dbxref = taxon: 9606; Name = 1; chromosome = 1; gbkey = Src; genome = chromosome; mol_type = геномная ДНК |
с разными парами, разделенными ;
и <tag><content>
пары, определенные с помощью =
.
Тогда цель состояла бы в том, чтобы сохранить остальные столбцы DataFrame и преобразовать этот в:
attribute.ID | attributes.Dbxref | attribute.Name | атрибуты. хромосома | attributes.gbkey | attributes.genome | attribute.mol_type |
---|---|---|---|---|---|---|
NC_000001.11: 1..248956422 | таксон: 9606 | 1 | 1 | Src | хромосома | геномная ДНК |
И то же самое для всех строк DataFrame.
Проблема:
Все решения, которые я нашел до сих пор, нет умело масштабируются, и их применение к входному файлу 3M занимает слишком много времени.
В последних трех случаях, которые я покажу, производительность кажется лучше, поскольку я могу протестировать до 100 КБ примерно за 4 секунды (при этом время составляет около 10 КБ строк в первых решениях), но это все еще низкая производительность для строк 3М.
Текущие решения:
Обратите внимание, что:
- Я запускаю это в Jupyter Notebook, поэтому
%timeit
вызов. - Я проверяю только до 10 тыс. Строк, чтобы увидеть, как он масштабируется (становится слишком длинным с большим количеством), за исключением последних 3 решений, поскольку они работают лучше (поэтому я рассчитываю их для 100 тыс. Строк)
1. Серии. Примените функцию, которая выполняет двойное разделение строк.
def expand_info_string1(df, column='info', entity_sep=';', id_sep='='):
def do_expand(cell, entity_seq, id_sep):
data = [x.split(id_sep) for x in str(cell).split(entity_seq)]
return pd.Series(list(zip(*data))[-1], index=list(zip(*data))[0])
info = df[column].apply(do_expand, entity_seq=entity_sep, id_sep=id_sep)
return pd.concat([df.drop(columns=[column]), info.add_prefix(f'{column}.')], axis=1).fillna('')
%timeit dd = expand_info_string1(df.sample(10), 'attributes')
%timeit dd = expand_info_string1(df.sample(100), 'attributes')
%timeit dd = expand_info_string1(df.sample(1000), 'attributes')
%timeit dd = expand_info_string1(df.sample(10000), 'attributes')
330 ms ± 267 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
299 ms ± 18.9 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
708 ms ± 375 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
4.4 s ± 263 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
2. Series.str.split + Series. Примените функцию, чтобы сделать 1 сплит
def expand_info_string3(df, column='info', entity_sep=';', id_sep='='):
def do_expand(cell, id_sep):
data = [str(x).split(id_sep) for x in cell]
return pd.Series(list(zip(*data))[-1], index=list(zip(*data))[0])
info = df[column].str.split(entity_sep).apply(do_expand, id_sep=id_sep)
return pd.concat([df.drop(columns=[column]), info.add_prefix(f'{column}.')], axis=1).fillna('')
%timeit dd = expand_info_string3(df.sample(10), 'attributes')
%timeit dd = expand_info_string3(df.sample(100), 'attributes')
%timeit dd = expand_info_string3(df.sample(1000), 'attributes')
%timeit dd = expand_info_string3(df.sample(10000), 'attributes')
90 ms ± 28.5 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
338 ms ± 66.5 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
553 ms ± 13.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
4.55 s ± 379 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
3. Series.str.split (expand = True) + Series. Примените функцию для разделения 1
def expand_info_string2(df, column='info', entity_sep=';', id_sep='='):
def do_expand(cell):
data = cell.dropna().values
return pd.Series(list(zip(*data))[-1], index=list(zip(*data))[0])
info = df[column].str.split(entity_sep, expand=True).apply(lambda cell: cell.str.split(id_sep), axis=1).apply(do_expand, axis=1)
return pd.concat([df.drop(columns=[column]), info.add_prefix(f'{column}.')], axis=1).fillna('')
%timeit dd = expand_info_string2(df.sample(10), 'attributes')
%timeit dd = expand_info_string2(df.sample(100), 'attributes')
%timeit dd = expand_info_string2(df.sample(1000), 'attributes')
%timeit dd = expand_info_string2(df.sample(10000), 'attributes')
236 ms ± 44.2 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
262 ms ± 19.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
1.35 s ± 281 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
11.2 s ± 313 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
4. Series.str.extractall + регулярное выражение
def expand_info_regex(df, column='info', entity_sep=';', id_sep='='):
info_re = re.compile("([^;]+?)=(?:([^;]+))?")
info= (df[column].str.extractall(info_re).reset_index(level=1, drop=True)
.set_index(0, append=True)[1]
.unstack(level=1))
return pd.concat([df.drop(columns=[column]), info.add_prefix(f'{column}.')], axis=1).fillna('')
%timeit dd = expand_info_regex(df.sample(10), 'attributes')
%timeit dd = expand_info_regex(df.sample(100), 'attributes')
%timeit dd = expand_info_regex(df.sample(1000), 'attributes')
%timeit dd = expand_info_regex(df.sample(10000), 'attributes')
%timeit dd = expand_info_regex(df.sample(100000), 'attributes')
122 ms ± 3.41 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
129 ms ± 3.38 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
157 ms ± 3.03 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
474 ms ± 9.87 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
4.28 s ± 33.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
5. Series.str.split (развернуть) .stack + DataFrame.groupby
def expand_info_stackgroup(df, column='info', entity_sep=';', id_sep='='):
info = (df[column].str.split(entity_sep, expand=True).stack()
.str.split(id_sep, expand=True).reset_index()
.drop(columns="level_1").groupby(['level_0', 0]).first()
.unstack(fill_value="").reset_index(drop=True))
info.columns = [x[1] for x in info.columns]
info.index = df.index
return pd.concat([df.drop(columns=[column]), info.add_prefix(f'{column}.')], axis=1)
%timeit dd = expand_info_stackgroup(df.sample(10), 'attributes')
%timeit dd = expand_info_stackgroup(df.sample(100), 'attributes')
%timeit dd = expand_info_stackgroup(df.sample(1000), 'attributes')
%timeit dd = expand_info_stackgroup(df.sample(10000), 'attributes')
%timeit dd = expand_info_stackgroup(df.sample(100000), 'attributes')
178 ms ± 24.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
269 ms ± 114 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
231 ms ± 87.2 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
494 ms ± 21.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
4.51 s ± 99.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
6. Series.str.split (развернуть) .stack + DataFrame.set_index
def expand_info_stackindex (df, column = ‘info’, entity_sep = ‘;’, id_sep = ‘=’):
info = (df[column].str.split(entity_sep, expand=True).stack()
.str.split(id_sep, expand=True).reset_index()
.drop(columns="level_1").set_index(['level_0', 0])
.unstack(fill_value="").reset_index(drop=True))
info.columns = [x[1] for x in info.columns]
info.index = df.index
return pd.concat([df.drop(columns=[column]), info.add_prefix(f'{column}.')], axis=1)
%timeit dd = expand_info_stackindex(df.sample(10), 'attributes')
%timeit dd = expand_info_stackindex(df.sample(100), 'attributes')
%timeit dd = expand_info_stackindex(df.sample(1000), 'attributes')
%timeit dd = expand_info_stackindex(df.sample(10000), 'attributes')
%timeit dd = expand_info_stackindex(df.sample(100000), 'attributes')
200 ms ± 91.7 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
133 ms ± 4.39 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
158 ms ± 4.02 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
467 ms ± 16.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
4.16 s ± 252 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
Любая помощь в попытке оптимизировать это, чтобы сделать это возможным для файла с большим количеством записей, была бы очень признательна. Спасибо!