我有一个命名元组列表。每个命名的元组都是我创建的DataPoint
类型,如下所示:
class DataPoint(NamedTuple):
data: float
location_zone: float
analysis_date: datetime
error: float
在我的代码的各个阶段,我必须通过特定属性来获取列表中的所有DataPoints
。这是我为analysis_date
做的方法,其他属性也有类似的功能:
def get_data_points_on_date(self, data_points, analysis_date):
data_on_date = []
for data_point in data_points:
if data_point.analysis_date == analysis_date:
data_on_date.append(data_point)
return data_on_date
在具有数千个点的列表上,这被称为> 100,000次,因此大大降低了我的脚本的运行速度。
我可以用字典代替列表,以显着提高速度,但是由于我需要搜索多个属性,因此没有明显的关键。我可能会选择占用最多时间的函数(在这种情况下为analysis_date
),并将其用作键。但是,这将大大增加我的代码的复杂性。除了散列之外/还有逃避我的聪明的散列方法?
答案 0 :(得分:1)
您是对的,如果可以将数据预先计算一次,则可以避免进行100,000次线性搜索。为什么不使用多个字典,每个字典都以不同的感兴趣属性为键?
每个字典将被预先计算一次:
self.by_date = defaultdict(list)
for point in data_points:
self.by_date[point.analysis_date].append(point)
现在,您的get_data_points_for_date
函数变成了单行代码:
def get_data_points_for_date(self, date):
return self.by_date[date]
您可能会完全删除此方法,而只需使用self.by_date[date]
。
这不会增加代码的复杂性,但是确实可以提前转移一些记账负担。您可以通过使用set_data方法来预先计算所需的所有字典来解决此问题:
from collections import defaultdict
from operator import attrgetter
def set_data(self, data_points):
keygetter):
d = defaultdict(list)
for point in data_points:
d[key(point)].append(point)
return d
self.by_date = make_dict(attrgetter('analysis_date'))
self.by_zone = make_dict(self.zone_code)
def zone_code(self, data_point):
return int(data_point.location_zone // 0.01)
要将zone_code
转换为整数,需要float
之类的东西,因为依靠float
作为键不是一个好主意。
答案 1 :(得分:1)
也许内存中的SQLite数据库(带有列索引)可能会有所帮助。它甚至可以按照Mapping result rows to namedtuple in python sqlite的描述将行映射到命名元组。
有关更完整的解决方案,请参见http://peter-hoffmann.com/2010/python-sqlite-namedtuple-factory.html。
一个基于上面两个链接的基本示例:
from typing import NamedTuple
from datetime import datetime
import sqlite3
class DataPoint(NamedTuple):
data: float
location_zone: float
analysis_date: datetime
error: float
def datapoint_factory(cursor, row):
return DataPoint(*row)
def get_data_points_on_date(cursor, analysis_date):
cursor.execute(
f"select * from datapoints where analysis_date = '{analysis_date}'"
)
return cursor.fetchall()
conn = sqlite3.connect(":memory:")
c = conn.cursor()
c.execute(
"create table datapoints "
"(data real, location_zone real, analysis_date text, error timestamp)"
)
c.execute(
"create index if not exists analysis_date_index on datapoints (analysis_date)"
)
timestamp = datetime.now().isoformat()
data_points = [
DataPoint(data=0.5, location_zone=0.1, analysis_date=timestamp, error=0.0)
]
for data_point in data_points:
c.execute(f"insert into datapoints values {tuple(data_point)}")
conn.commit()
c.close()
conn.row_factory = datapoint_factory
c = conn.cursor()
print(get_data_points_on_date(c, timestamp))
# [DataPoint(data=0.5, location_zone=0.1, analysis_date='2019-07-19T20:37:38.309668', error=0)]
c.close()
答案 2 :(得分:1)
numpy 和 pandas 已针对这些内容进行了优化,并且速度非常快。
我在下面的代码中为您做了一个简单的比较测试,以了解熊猫 DataFrame 在速度方面的优势:
代码
import pandas as pd
import numpy as np
from time import perf_counter
# init
a = np.array([0 if 500 < i < 510 else 1 for i in range(100, 1000000)])
data_points = {'data': np.arange(100, 1000000),
'location_zone': np.arange(100, 1000000),
'analysis_date': np.arange(100, 1000000) * a,
'error': np.arange(100, 1000000)}
df = pd.DataFrame(data_points)
# speed of dataframe
t0 = perf_counter()
b = df[df['analysis_date'] == 0]
print("pandas DataFrame took: {:.4f} sec".format(perf_counter() - t0))
print(b)
# speed normal python code
t0 = perf_counter()
indices = [d for d in range(data_points['analysis_date'].shape[0]) if data_points['analysis_date'][d] == 0]
print("normal python code took: {:.4f} sec".format(perf_counter() - t0))
print(indices)
输出
pandas DataFrame took: 0.0049 sec
analysis_date data error location_zone
401 0 501 501 501
402 0 502 502 502
403 0 503 503 503
404 0 504 504 504
405 0 505 505 505
406 0 506 506 506
407 0 507 507 507
408 0 508 508 508
409 0 509 509 509
normal python code took: 0.2782 sec
[401, 402, 403, 404, 405, 406, 407, 408, 409]
pandas DataFrame参考:Link
关于DataFrames的好教程:Link
答案 3 :(得分:0)
以下代码:
def get_data_points_on_date(self, data_points, analysis_date):
data_on_date = []
for data_point in data_points:
if data_point.analysis_date == analysis_date:
data_on_date.append(data_point)
return data_on_date
可以重构为:
def get_data_points_on_date(self, data_points, analysis_date):
return (p for p in data_points if p.analysis_date == analysis_date)
您可以在for循环中访问返回的值,也可以使用list(returned_value)
使其成为列表。
答案 4 :(得分:0)
如果有此类DataPoints的列表,则可以使用pandas
和MultiIndex通过O(1)查找使其可访问:
import pandas as pd
datapoints_series = pd.DataFrame(
{
"data": pt.data,
"location_zone": pt.location_zone,
"analysis_date": pt.analysis_date,
"error": pt.error,
"data_point": pt
}
for pt in data_points_list
).set_index([
"data",
"location_zone",
"analysis_date",
"error"
]).squeeze() # send to Series
要访问特定日期:
def date_accessor(date):
idx = pd.IndexSlice[:, :, date, :]
date = "2019-07-01"
datapoints_series.loc[date_accessor(date)]
如果您想再次在列表中添加数据点,只需在最后一行附加一个.tolist()
方法调用即可。