pandas corr和corrwith非常缓慢

6

我有一个包含不到30K行、7列的pandas数据框,我正在尝试获得其中4列与第五列之间的相关性。问题在于,我想在海量数据集上进行计算,但这需要大约40秒的时间才能运行。以下是我的代码:

df_a = dfr[['id', 'state', 'perform', 'A']].groupby(['id', 'state']).corr().ix[1::2][['A']].reset_index(2).drop('level_2', axis=1)
df_b = dfr[['id', 'state', 'perform', 'B']].groupby(['id', 'state']).corr().ix[1::2][['B']].reset_index(2).drop('level_2', axis=1)
df_c = dfr[['id', 'state', 'perform', 'C']].groupby(['id', 'state']).corr().ix[1::2][['C']].reset_index(2).drop('level_2', axis=1)
df_d = dfr[['id', 'state', 'perform', 'D']].groupby(['id', 'state']).corr().ix[1::2][['D']].reset_index(2).drop('level_2', axis=1)

df = df_a.merge(df_b, left_index=True, right_index=True)
df = df.merge(df_c, left_index=True, right_index=True)
df = df.merge(df_d, left_index=True, right_index=True)

示例数据如下:

ID   State   perform   A   B   C   D
234   AK     75.8456   1   0   0   0
284   MN     78.6752   0   0   1   0

有没有任何方法可以让这个过程更快,或者更好地实现这个方法?

谢谢!


2
不确定你为什么要这样做,df[cols].corrwith(series)有什么问题吗? - cs95
1
你的数据都是浮点数吗?尝试使用df.dtypes。在我的电脑上几乎是立即完成的:pandas.np.corrcoef(pandas.np.random.rand(5,30000))。 - alex314159
@cᴏʟᴅsᴘᴇᴇᴅ 我正在进行这个分组,因为我的性能列对于每个分组都是特定的,并且将它们与一个Series进行比较也更慢。我之前的方法就是你说的那样,但比这个方法慢了12秒。我的第一个尝试看起来像这样: - TMarks
2
你能否链接到你的数据或发布数据的子集,以便我们看到它的样子? - alex314159
第三步,将 .reset_index(2).drop('level_2', axis = 1) 改为 .reset_index(2, drop=True)。这样就可以少一个链接的函数,但是能够完成同样的操作。 - Ian Thompson
显示剩余7条评论
4个回答

9

pandas的corr函数之所以非常慢,是因为它会考虑NAN值:这基本上是一个Cython for-loop。

如果你的数据中没有NAN值,numpy.corrcoef会快得多


6

我想在这里支持ma-ming的答案...

相对于pandas,numpy的速度非常快。我进行了一些测试,以便了解它有多快... 速度显著提高

import timeit
import numpy as np
import pandas as pd


holdings_array = np.arange(40, 100, 20)
dates_array = [200, 800]

calc = pd.DataFrame(
    index=pd.MultiIndex.from_product(
        [['Correlation', 'Returns'], ['pandas', 'numpy', 'pandas.values'], dates_array],
        names=['Method', 'Library', 'Shape']),
    columns=holdings_array
).unstack(level=-1)

print("Checking pandas vs numpy... (ms)")

# PANDAS
for num_holdings in holdings_array:
    for num_dates in dates_array:
        df = pd.DataFrame(np.random.random(size=[num_dates, num_holdings]))
        ls = np.random.random(size=num_holdings)
        calc.loc[('Correlation', 'pandas'), (num_holdings, num_dates)] \
            = str(np.round(timeit.timeit('df.corr()', number=100, globals=globals()) * 10, 3))
        calc.loc[('Returns', 'pandas'), (num_holdings, num_dates)] \
            = str(np.round(timeit.timeit('(df*ls).sum(axis=1)', number=100, globals=globals()) * 10, 3))


# # NUMPY
# for x, num_holdings in enumerate(holdings_array):
#     for y, num_dates in enumerate(dates_array):
#         df = np.array(np.random.random(size=[num_dates, num_holdings]))
#         ls = np.random.random(size=num_holdings)
#         calc.loc[('Correlation', 'numpy'), (num_holdings, num_dates)] \
#             = str(np.round(timeit.timeit('np.corrcoef(df)', number=100, globals=globals()) * 10, 3))
#         calc.loc[('Returns', 'numpy'), (num_holdings, num_dates)] \
#             = str(np.round(timeit.timeit('(df*ls).sum(axis=1)', number=100, globals=globals()) * 10, 3))

# PANDAS.VALUES WITH NP FUNCTIONS
for num_holdings in holdings_array:
    for num_dates in dates_array:
        df = pd.DataFrame(np.random.random(size=[num_dates, num_holdings]))
        ls = np.random.random(size=num_holdings)
        calc.loc[('Correlation', 'pandas.values'), (num_holdings, num_dates)] \
            = str(np.round(timeit.timeit('np.corrcoef(df.values, rowvar=False)', number=100, globals=globals()) * 10, 3))
        calc.loc[('Returns', 'pandas.values'), (num_holdings, num_dates)] \
            = str(np.round(timeit.timeit('(df.values*ls).sum(axis=1)', number=100, globals=globals()) * 10, 3))

print(f"Results: \n{calc.to_string()}")

如果在 Pandas 数据框上进行向量计算时,尽可能将其转换为 df.values,并运行 np 操作。

例如,我可以将 df.corr() 更改为 np.corrcoef(df.values, rowvar=False)(注意:rowvar=False 很重要,以便正确处理形状),对于大型操作,您将看到 10 倍、100 倍的速度提升。非常重要。

enter image description here


我最终发现了您在这里提到的内容,使用 pandas 很难实现,速度非常慢,我被迫使用 numpy。时间节省非常明显,而且代码改动很小。 - TMarks
1
感谢JBB和Ma Ming建议使用numpy。我正在处理(800000 X 2354)的数据。.corr()无法完成,但np.corrcoef(df.values, rowvar=False)在15秒内完成了。太棒了! - Scott85044

1

我将您的原始代码与我的代码进行了快速比较,以下是时间差异:

您的代码

%%timeit

# data
'''
ID   State   perform   A   B   C   D
234   AK     75.8456   1   0   0   0
284   MN     78.6752   0   0   1   0
'''

# make dataframe
dfr = pd.read_clipboard()

df_a = dfr[['ID', 'State', 'perform', 'A']].groupby(['ID', 'State']).corr().ix[1::2][['A']].reset_index(2).drop('level_2', axis=1)
df_b = dfr[['ID', 'State', 'perform', 'B']].groupby(['ID', 'State']).corr().ix[1::2][['B']].reset_index(2).drop('level_2', axis=1)
df_c = dfr[['ID', 'State', 'perform', 'C']].groupby(['ID', 'State']).corr().ix[1::2][['C']].reset_index(2).drop('level_2', axis=1)
df_d = dfr[['ID', 'State', 'perform', 'D']].groupby(['ID', 'State']).corr().ix[1::2][['D']].reset_index(2).drop('level_2', axis=1)

df = df_a.merge(df_b, left_index=True, right_index=True)
df = df.merge(df_c, left_index=True, right_index=True)
df = df.merge(df_d, left_index=True, right_index=True)

your time

“我的代码”
%%timeit

# data
'''
ID   State   perform   A   B   C   D
234   AK     75.8456   1   0   0   0
284   MN     78.6752   0   0   1   0
'''

# make dataframe
df = pd.read_clipboard()

# make other dfs
df_a = df.loc[:, :'A'].groupby([
    'ID',
    'State'
]).corr().iloc[1::2][['A']].reset_index(2, drop = True)

df_b = df.loc[:, [
    'ID',
    'State',
    'perform',
    'B'
]].groupby([
    'ID',
    'State'
]).corr().iloc[1::2][['B']].reset_index(2, drop = True)

df_c = df.loc[:, [
    'ID',
    'State',
    'perform',
    'C'
]].groupby([
    'ID',
    'State'
]).corr().iloc[1::2][['C']].reset_index(2, drop = True)

df_d = df.loc[:, [
    'ID',
    'State',
    'perform',
    'D'
]].groupby([
    'ID',
    'State'
]).corr().iloc[1::2][['D']].reset_index(2, drop = True)

# concat them together
pd.concat([df_a, df_b, df_c, df_d], axis = 1)

my time

虽然差异可能仍然可以忽略不计。

编辑

决定尝试使用 for 循环来消除重复的代码。运行时间有所改善:

%%timeit

# data
'''
ID   State   perform   A   B   C   D
234   AK     75.8456   1   0   0   0
284   MN     78.6752   0   0   1   0
'''

# make dataframe
df = pd.read_clipboard()

# make list of letter columns
letters = ['A', 'B', 'C', 'D']

# store corr() dfs in list for concatenation
list_of_dfs = []
for letter in letters:
    list_of_dfs.append(df.loc[:, [
        'ID',
        'State',
        'perform',
        letter
    ]].groupby([
        'ID',
        'State'
    ]).corr().iloc[1::2][[letter]].reset_index(2, drop = True))

# concat them together
pd.concat(list_of_dfs, axis = 1)

edited output


我很感激你的尝试,但是在我的机器上并没有运行得更快。不过,我确实采纳了你在其他评论中提出的建议! - TMarks
2
我能想到的最后一件事是 - 你可以尝试使用数据子集吗?比如说500行,然后测量需要多长时间?如果30000行需要的时间比500行的时间长得多,那么可能存在内存问题,你需要分块处理数据。 - alex314159
@alex314159 感谢您的建议,但我相信问题出在pandas上,因为我已经重写了我的函数,使用numpy进行相关性计算,速度更快了。 - TMarks

1

虽然可能不是最好的解决方案,但这对我起作用,并将运行时间从之前的52秒降至总共4.8秒。

我最终使用Pandas进行分组,然后使用Numpy运行相关性。

groups = df.groupby(['course_id', 'activity_id'])
np_arr = []

for (cor_id,act_id), group in groups:

    np_arr.append([cor_id, act_id,
                   np.corrcoef(group.A.as_matrix(), group.perform.as_matrix())[0,1],
                   np.corrcoef(group.B.as_matrix(), group.perform.as_matrix())[0,1],
                   np.corrcoef(group.C.as_matrix(), group.perform.as_matrix())[0,1],
                   np.corrcoef(group.D.as_matrix(), group.perform.as_matrix())[0,1]])

df = pd.DataFrame(data=np.array(np_arr), columns=['course_id', 'activity_id', 'A', 'B', 'C', 'D'])

这种方法有效地降低了我的运行时间,我打算使用Cython编写变量来进一步提高速度。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接