算法效率-使用熊猫的数据处理效率(三个嵌套循环)

时间:2018-07-16 23:40:10

标签: python algorithm pandas data-science processing-efficiency

该数据分为两个数据集,我需要检查第一个数据集中在特定位置上的单个时间事件是否与时间范围在同一特定位置的第二个数据集中,并在满足条件的情况下将第二组的ID相应地附加到第一组。我有要检查的特定位置列表。

我的问题是,第一个数据集包含大约500,000行,第二个数据集包含大约90,000行。运行这两个数据集将花费很多时间,并且我的计算能力受到限制。

这是Python代码:

import datetime
import pandas as pd

def assign_tRangeID(singleEventDF, timeRangeDF):
    margin = datetime.timedelta(minutes=15)
    for i, single in singleEventDF.iterrows():
        for j, timeRange in timeRangeDF.iterrows():
           if timeRange['start_time']-margin <= single['singleEvent_time'] <= timeRange['end_time']
               singleEventDF.at[i, 'tRange_ID'] = timeRangeDF['ID']

for i, location in location_list.iterrows():
    single_subset = singleEvent['loc'].loc[[singleEvent['loc'] = location['loc']]
    tRange_subset = timeRange['loc'].loc[[timeRange['loc'] = location['loc']]
    assign_eventID(single_subset, tRange_subset)

我是Python的初学者,所以我想知道是否可以以更有效的方式执行此操作,而不必使用数据库或大数据解决方案。感谢您的所有帮助!

2 个答案:

答案 0 :(得分:0)

下面的代码创建两个数据框。然后根据room_id合并它们。然后创建附加列flag,以为每个event_id标识login_time是否落在start_timeend_time之间。

导入库

import pandas as pd
import numpy as np
#import datetime
from datetime import datetime, timedelta
import hashlib

创建示例数据框

# Create dataframe - 1
room_id = abs(np.floor(np.random.randn(100)*100))
login_time = np.random.random() * timedelta(days=1)
temp1 = abs(np.floor(np.random.randn(100)*100))

df1 = pd.DataFrame({'room_id': room_id, 'login_time':login_time, 'temp1':temp1})
df1['login_time'] = df1.apply(lambda x: x['login_time'] + timedelta(hours=x['temp1']), axis=1)
del df1['temp1']

enter image description here

# Create dataframe - 2
room_id = abs(np.floor(np.random.randn(100)*100))
event_id = np.random.randn(100)*100
start_time = np.random.random() * timedelta(days=1)
temp2 = abs(np.floor(np.random.randn(100)*100))
temp3 = abs(np.floor(np.random.randn(100)*100))
df2 = pd.DataFrame({'room_id': room_id, 'event_id': event_id, 'start_time': start_time, 'temp2':temp2,'temp3':temp3})  
df2['start_time'] = df2.apply(lambda x: x['start_time'] + timedelta(hours=x['temp2']), axis=1)
df2['end_time'] = df2.apply(lambda x: x['start_time'] + timedelta(hours=x['temp3']), axis=1)
df2['event_id'] = df2.apply(lambda x: hashlib.md5(str(x['event_id']).encode('utf-8')).hexdigest(), axis=1)
del df2['temp2']
del df2['temp3'] 

enter image description here

# Merge two dataframes
df = df1.merge(df2, on='room_id', how='inner')
df.head(2)

enter image description here

检查登录时间是否在开始时间和结束时间之间

注意:这里,flag == 1表示登录时间介于开始时间和结束时间之间

df['flag'] = np.where((df['login_time'] >= df['start_time']) & (df['end_time'] >= df['login_time']), 1, 0)

enter image description here

使用flag==0过滤掉所有事件,仅保留flag==1

df = df[df['flag']==1]
df.head(5)

enter image description here

答案 1 :(得分:0)

当剥离DataFrame机械时,这是一个有趣的算法问题。要回答您的问题,可以更快地进行。我将稍微重申一下您的问题,以便该解决方案可以更适用于更多人。对其进行重构以使其适合您正在使用的数据结构,无需花费太多工作。

在开始之前,我想指出@NileshIngle的代码可以大大提高您的速度(我还没有进行基准测试),但是对于每种情况,时间复杂度仍然是二次的,而不仅仅是最差的情况。这个事实隐藏在他使用的各种pandas函数调用中,但是代码总是每次都在每个时间范围内接触。考虑到您提到的数据集的大小,除非在非常特殊的情况下,否则不太可能是您要寻找的解决方案。

免责声明:如果m和n的大小分别为nlog(n)+ mlog(m),则我认为此问题可以用最坏情况的复杂度解决。各自的输入。我的解决方案平均可以实现这种复杂性,但在最坏的情况下却无法实现。任何人都想提出更好的东西吗?

给出一个单次列表和一个时间范围列表,例如

single_times = [4, 5, 2, 3, -1]
time_ranges = [(1, 5), (10, 11), (2, 3)]

我们可以设计比O(len(t)len(r))更快的算法,该算法为t中的每个元素输出r中每个匹配时间范围的索引吗?对于此问题(考虑到您的示例包括端点),该输出将是:

res = [[0], [0], [0, 2], [0, 2], []]

乍看之下,问题似乎在于,对于single_times的每个元素,我们都必须检查time_ranges的每个元素,从而导致大量数据的荒谬运行时。对于要合并两个列表的常规数据,无法避免这种二次运行时间。但是,我们可以轻松地对这两个列表进行排序的事实为我们提供了更好的计算范围。

探索这个想法,如果single_times升序排序会怎样?例如,如果我们知道对应于3的时间范围是[(1,5),(2,3)],而我们想知道对应于4的时间范围怎么办?由于结束时间(2,3)小于3,我们失去了范围4,并且我们没有获得更多的时间范围。

我们将继续采用该思想,以创建一个基本的基于排序的算法,尝试将时间范围与时间进行匹配。在您的应用程序中,只要您具有对象引用,您实际上并不需要返回值具有相同的顺序,但是我们将继续跟踪所有内容的原始位置。给出选择后,我将使用numpy来提高效率和各种便利功能,但原始Python更具可移植性。

import itertools as it

def matching_times(single_times, time_ranges):
    single_index = sorted(xrange(len(single_times)), key=lambda i: single_times[i])
    single_times_sorted = [single_times[i] for i in single_index]
    time_ranges_sorted = sorted([(i, v[0], v[1]) for i, v in enumerate(time_ranges)], key=lambda w: w[1])

    m = 0  # keep track of min location in time_ranges_sorted
    res = [[]]

    # Find solutions for single_times_sorted[0]
    for i, w in enumerate(time_ranges_sorted):
        if w[1] > single_times_sorted[0]:
            break
        if w[2] >= single_times_sorted[0]:
            res[0].append(w)
            m = i+1

    for cur_time in it.islice(single_times_sorted, 1, len(single_times_sorted)):
        # Keep previous solutions that don't end too soon
        res.append([w for w in res[-1] if w[2]>=cur_time])

        # Strip extraneous information as soon as possible to preserve a semblance
        # of memory efficiency
        res[-2] = [w[0] for w in res[-2]]

        for i, w in enumerate(it.islice(time_ranges_sorted, m, len(time_ranges_sorted)), m):
            if w[1] > cur_time:
                break
            if w[2] >= cur_time:
                res[-1].append(w)
                m = i+1

    # Strip remaining extra information from solution
    res[-1] = [w[0] for w in res[-1]]

    # Re-sort result according to original locations in single_times
    return [v[1] for v in sorted(enumerate(res), key=lambda v: single_index[v[0]])]

然后可以非常简单地获得所需的解决方案:

res = matching_times(single_times, time_ranges); res
>>> [[0], [0], [0, 2], [0, 2], []]

这仍然具有最坏情况的二次时间复杂度,但是对于现实世界的数据而言,相对于时间范围的总数,每个时间的匹配时间范围可能不多,预期运行时间将更接近O( nlog(n)+ mlog(m)),其中m和n分别是两个输入列表的长度。