Панды разбивают столбец на несколько: улучшение производительности

Цель Кодекса:

Цель функции, над которой я работаю, — взять определенный столбец из 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.IDattributes.Dbxrefattribute.Nameатрибуты. хромосомаattributes.gbkeyattributes.genomeattribute.mol_type
NC_000001.11: 1..248956422таксон: 960611Srcхромосомагеномная ДНК

И то же самое для всех строк 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)

Любая помощь в попытке оптимизировать это, чтобы сделать это возможным для файла с большим количеством записей, была бы очень признательна. Спасибо!

0

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *