Оптимизация декартового произведения между двумя Pandas данными - PullRequest
6 голосов
/ 22 января 2020

У меня есть два кадра данных с одинаковыми столбцами:

Кадр данных 1 :

          attr_1  attr_77 ... attr_8
userID                              
John      1.2501  2.4196  ... 1.7610
Charles   0.0000  1.0618  ... 1.4813
Genarito  2.7037  4.6707  ... 5.3583
Mark      9.2775  6.7638  ... 6.0071

Кадр данных 2 :

          attr_1  attr_77 ... attr_8
petID                              
Firulais  1.2501  2.4196  ... 1.7610
Connie    0.0000  1.0618  ... 1.4813
PopCorn   2.7037  4.6707  ... 5.3583

Я хочу создать корреляцию и фрейм данных p-значения всех возможных комбинаций, это будет результатом:

   userId   petID      Correlation    p-value
0  John     Firulais   0.091447       1.222927e-02
1  John     Connie     0.101687       5.313359e-03
2  John     PopCorn    0.178965       8.103919e-07
3  Charles  Firulais   -0.078460      3.167896e-02

Проблема в том, что декартово произведение генерирует более 3 миллионов кортежи. Отнимите минуты до финиша sh. Это мой код, я написал две альтернативы:

Прежде всего, исходные кадры данных :

df1 = pd.DataFrame({
    'userID': ['John', 'Charles', 'Genarito', 'Mark'],
    'attr_1': [1.2501, 0.0, 2.7037, 9.2775],
    'attr_77': [2.4196, 1.0618, 4.6707, 6.7638],
    'attr_8': [1.7610, 1.4813, 5.3583, 6.0071]
}).set_index('userID')

df2 = pd.DataFrame({
    'petID': ['Firulais', 'Connie', 'PopCorn'],
    'attr_1': [1.2501, 0.0, 2.7037],
    'attr_77': [2.4196, 1.0618, 4.6707],
    'attr_8': [1.7610, 1.4813, 5.3583]
}).set_index('petID')

Опция 1 :

# Pre-allocate space
df1_keys = df1.index
res_row_count = len(df1_keys) * df2.values.shape[0]
genes = np.empty(res_row_count, dtype='object')
mature_mirnas = np.empty(res_row_count, dtype='object')
coff = np.empty(res_row_count)
p_value = np.empty(res_row_count)

i = 0
for df1_key in df1_keys:
    df1_values = df1.loc[df1_key, :].values
    for df2_key in df2.index:
        df2_values = df2.loc[df2_key, :]
        pearson_res = pearsonr(df1_values, df2_values)

        users[i] = df1_key
        pets[i] = df2_key
        coff[i] = pearson_res[0]
        p_value[i] = pearson_res[1]
        i += 1

# After loop, creates the resulting Dataframe
return pd.DataFrame(data={
    'userID': users,
    'petID': pets,
    'Correlation': coff,
    'p-value': p_value
})

Вариант 2 (медленнее) , начиная с здесь :

# Makes a merge between all the tuples
def df_crossjoin(df1_file_path, df2_file_path):
    df1, df2 = prepare_df(df1_file_path, df2_file_path)

    df1['_tmpkey'] = 1
    df2['_tmpkey'] = 1

    res = pd.merge(df1, df2, on='_tmpkey').drop('_tmpkey', axis=1)
    res.index = pd.MultiIndex.from_product((df1.index, df2.index))

    df1.drop('_tmpkey', axis=1, inplace=True)
    df2.drop('_tmpkey', axis=1, inplace=True)

    return res

# Computes Pearson Coefficient for all the tuples
def compute_pearson(row):
    values = np.split(row.values, 2)
    return pearsonr(values[0], values[1])

result = df_crossjoin(mrna_file, mirna_file).apply(compute_pearson, axis=1)

Есть ли более быстрый способ решить такую ​​проблему с Pandas? Или у меня не будет больше возможностей, чем распараллеливать итерации?

Редактировать:

По мере увеличения размера кадра данных второй вариант приводит к лучшему времени выполнения , но До финиша sh.

все еще требуется несколько секунд * Спасибо заранее

Ответы [ 2 ]

3 голосов
/ 27 января 2020

Из всех протестированных альтернатив, тот, который дал мне лучшие результаты, был следующим:

  1. Итерационный продукт был сделан с itertools.product () .

  2. Все итерации на обоих iterrows были выполнены для пула параллельных процессов (с использованием функции map ).

Чтобы повысить производительность, функция compute_row_cython была скомпилирована с Cython , как рекомендуется в этом разделе документации Pandas :

В файле cython_modules.pyx:

from scipy.stats import pearsonr
import numpy as np

def compute_row_cython(row):
    (df1_key, df1_values), (df2_key, df2_values) = row
    cdef (double, double) pearsonr_res = pearsonr(df1_values.values, df2_values.values)
    return df1_key, df2_key, pearsonr_res[0], pearsonr_res[1]

Затем я настроил setup.py:

from distutils.core import setup
from Cython.Build import cythonize

setup(name='Compiled Pearson',
      ext_modules=cythonize("cython_modules.pyx")

Наконец я скомпилировал его: python setup.py build_ext --inplace

Финальный код был оставлен, затем:

import itertools
import multiprocessing
from cython_modules import compute_row_cython

NUM_CORES = multiprocessing.cpu_count() - 1

pool = multiprocessing.Pool(NUM_CORES)
# Calls to Cython function defined in cython_modules.pyx
res = zip(*pool.map(compute_row_cython, itertools.product(df1.iterrows(), df2.iterrows()))
pool.close()
end_values = list(res)
pool.join()

Ни Dask, ни функция merge с использованным apply не дали мне лучших результатов. Даже не оптимизировать применение с Cython. Фактически, эта альтернатива с этими двумя методами вызвала ошибку памяти, при реализации решения с помощью Dask мне пришлось сгенерировать несколько разделов, которые снизили производительность, поскольку пришлось выполнять много операций ввода-вывода.

Решение с Dask можно найти в моем другом вопросе .

1 голос
/ 22 января 2020

Вот еще один метод, использующий то же перекрестное соединение, но использующий встроенный метод pandas DataFrame.corrwith и scipy.stats.ttest_ind. Поскольку мы используем менее «зацикленную» реализацию, это должно работать лучше.

from scipy.stats import ttest_ind

mrg = df1.assign(key=1).merge(df2.assign(key=1), on='key').drop(columns='key')

x = mrg.filter(like='_x').rename(columns=lambda x: x.rsplit('_', 1)[0])
y = mrg.filter(like='_y').rename(columns=lambda x: x.rsplit('_', 1)[0])

df = mrg[['userID', 'petID']].join(x.corrwith(y, axis=1).rename('Correlation'))

df['p_value'] = ttest_ind(x, y, axis=1)[1]
      userID     petID  Correlation   p_value
0       John  Firulais     1.000000  1.000000
1       John    Connie     0.641240  0.158341
2       John   PopCorn     0.661040  0.048041
3    Charles  Firulais     0.641240  0.158341
4    Charles    Connie     1.000000  1.000000
5    Charles   PopCorn     0.999660  0.020211
6   Genarito  Firulais     0.661040  0.048041
7   Genarito    Connie     0.999660  0.020211
8   Genarito   PopCorn     1.000000  1.000000
9       Mark  Firulais    -0.682794  0.006080
10      Mark    Connie    -0.998462  0.003865
11      Mark   PopCorn    -0.999569  0.070639
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...