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

Python 算法效率-使用熊猫的数据处理效率(三个嵌套循环),python,algorithm,pandas,data-science,processing-efficiency,Python,Algorithm,Pandas,Data Science,Processing Efficiency,数据分为两个数据集,我需要检查第一个数据集中位于特定位置的单一时间事件是否与第二个数据集中位于同一特定位置的时间范围一致,如果满足条件,则将第二组的ID相应地附加到第一组。我有一个要检查的特定位置的列表 我的问题是第一个数据集包含大约500000行,第二个数据集包含大约90000行。运行这两个数据集需要很长时间,而且我的计算能力有限 以下是Python代码: import datetime import pandas as pd def assign_tRangeID(singleEventD

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

我的问题是第一个数据集包含大约500000行,第二个数据集包含大约90000行。运行这两个数据集需要很长时间,而且我的计算能力有限

以下是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)
导入日期时间
作为pd进口熊猫
def assign_tRangeID(单事件DF,时间范围DF):
边距=datetime.timedelta(分钟=15)
对于i,single-in-singleEventDF.iterrows():
对于j,timeRangeDF.ItErrors()中的时间范围:

如果时间范围['start_time']-margin下面的代码创建两个数据帧。然后根据
房间id
合并这些文件。然后,如果
登录时间
介于
开始时间
结束时间
之间,则会创建附加列
标志
,以识别每个
事件id

导入库

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']

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

# 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']
注意:这里,flag==1表示登录时间介于开始时间和结束时间之间

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

过滤掉
标志==0的所有事件,只保留
标志==1

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

当您剥离数据帧机制时,这是一个有趣的算法问题。要回答您的问题,这可以更快。我要稍微重申一下你的问题,这样解决方案可以更适用于更多的人。重构它以适应您正在使用的数据结构不需要太多工作

在开始之前,我想指出@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], []]
乍一看,问题似乎是对于
单个时间的每个元素
我们必须检查
时间范围的每个元素
,导致大量数据的荒谬运行时间。对于想要合并两个列表的一般类型的数据,无法避免这种二次运行时。然而,我们可以轻松地对这两个列表进行排序,这一事实为我们提供了更好的计算边界

探索这个想法,如果
单次_次
按升序排序会发生什么?例如,如果我们知道与
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分别表示两个输入列表的长度。

我对您的问题描述不清楚,即使是清楚的,它也非常模糊,对其他任何人都没有用处。你能编辑你的问题,把它归结为一个更简单的问题,保留你需要的所有特征吗?当然,对不起。我将设法把它归结为一个更简单的问题。谢谢@Daniel在回答问题时请检查。@HansMusgrave谢谢,我在回答问题时会检查。@Daniel我喜欢你提供的措辞。代码可以更一般一些,但我将继续写一个解决方案。嗨@Hans,谢谢你的解决方案!明天我得仔细看一看,写一个更好的回复,但我只想说谢谢你花时间回答!
res = matching_times(single_times, time_ranges); res
>>> [[0], [0], [0, 2], [0, 2], []]