1 回答
TA贡献1828条经验 获得超13个赞
您的代码很慢有两个原因:
您做了不必要的工作,并使用了低效的 Python 语句。您不使用
veh_id
但仍用于int()
转换它。您创建一个空字典只是为了在单独的语句中在其中设置 4 个键,您使用单独的str()
和round()
调用以及字符串连接,其中字符串格式化可以一步完成所有工作,您重复引用.attrib
,因此 Python 必须重复查找该字典属性为你。当用于每个单独的 (x, y) 坐标时,
sumolib.net.convertXY2LonLat()
实现效率非常低;pyproj.Proj()
它每次都从头开始加载偏移量和对象。pyproj.Proj()
例如,我们可以通过缓存实例来切断这里的重复操作。或者我们可以避免使用它,或者通过一步处理所有坐标来使用它一次。
第一个问题可以通过删除不必要的工作和缓存属性字典之类的东西、只使用一次以及在函数参数中缓存重复的全局名称查找来避免(本地名称使用起来更快);关键字纯粹是_...
为了避免查找全局变量:
from operator import itemgetter
_fields = ('CO2', 'CO', 'NOx', 'PMx')
def aggregate(
vehicle,
_fields=_fields,
_get=itemgetter(*_fields, 'x', 'y'),
_conv=net.convertXY2LonLat,
):
# convert all the fields we need to floats in one step
*values, x, y = map(float, _get(vehicle.attrib))
# convert the coordinates to latitude and longitude
lng, lat = _conv(x, y)
# get the aggregation dictionary (start with an empty one if missing)
data = raw_pollution_data.setdefault(
f"{lng:.4f},{lat:.4f}",
dict.fromkeys(_fields, 0.0)
)
# and sum the numbers
for f, v in zip(_fields, values):
data[f] += v
为了解决第二个问题,我们可以用至少重用Proj()实例的东西来替换位置查找;在这种情况下,我们需要手动应用位置偏移:
proj = net.getGeoProj()
offset = net.getLocationOffset()
adjust = lambda x, y, _dx=offset[0], _dy=offset[1]: (x - _dx, y - _dy)
def longlat(x, y, _proj=proj, _adjust=adjust):
return _proj(*_adjust(x, y), inverse=True)
然后通过替换_conv本地名称在聚合函数中使用它:
def aggregate(
vehicle,
_fields=_fields,
_get=itemgetter(*_fields, 'x', 'y'),
_conv=longlat,
):
# function body stays the same
这仍然会很慢,因为它要求我们(x, y)分别转换每一对。
这取决于所使用的确切投影,但您可以简单地量化x并y坐标自己进行分组。您将首先应用偏移量,然后将坐标“四舍五入”,转换和舍入将实现的量相同。在投影(1, 0)和(0, 0)取经度差时,我们知道投影使用的粗略转换率,然后将其除以 10.000 就可以得出聚合区域的大小x和y值:
(proj(1, 0)[0] - proj(0, 0)[0]) / 10000
对于标准的 UTM 投影,它给了我大约11.5,因此将x和y坐标乘以该因子应该可以得到大致相同数量的分组,而不必对每个时间步长数据点进行完整的坐标转换:
proj = net.getGeoProj()
factor = abs(proj(1, 0)[0] - proj(0, 0)[0]) / 10000
dx, dy = net.getLocationOffset()
def quantise(v, _f=factor):
return v * _f // _f
def aggregate(
vehicle,
_fields=_fields,
_get=itemgetter(*_fields, 'x', 'y'),
_dx=dx, _dy=dy,
_quant=quantise,
):
*values, x, y = map(float, _get(vehicle.attrib))
key = _quant(x - _dx), _quant(y - _dy)
data = raw_pollution_data.setdefault(key, dict.fromkeys(_fields, 0.0))
for f, v in zip(_fields, values):
data[f] += v
对于问题中共享的非常有限的数据集,这给了我相同的结果。
但是,如果投影在经度上不同,这可能会导致地图上不同点的结果失真。我也不知道您究竟需要如何聚合整个区域的车辆坐标。
如果您真的只能按经度和纬度 1/10000 度的区域进行聚合,那么如果您将整个 numpy 数组输入到net.convertXY2LonLat(). 这是因为接受数组来批量pyproj.Proj()转换坐标,节省了大量时间,避免进行数十万次单独的转换调用,我们只需要进行一次调用。
与其使用 Python 字典和浮点对象来处理这个问题,不如在这里真正使用 Pandas DataFrame。它可以轻松地从每个元素属性字典中获取字符串(使用具有所有所需键的operator.itemgetter()对象可以非常快速地为您提供这些值),并在摄取数据时将所有这些字符串值转换为浮点数。这些值以紧凑的二进制形式存储在连续内存中,11800 行坐标和数据条目在这里不会占用太多内存。
因此,首先将您的数据加载到 DataFrame中,然后从该对象中一步转换您的 (x, y) 坐标,然后使用Pandas 分组功能按区域聚合值:
from lxml import etree
import pandas as pd
import numpy as np
from operator import itemgetter
def extract_attributes(context, fields):
values = itemgetter(*fields)
for _, elem in context:
yield values(elem.attrib)
elem.clear()
while elem.getprevious() is not None:
del elem.getparent()[0]
del context
def parse_emissions(filename):
context = etree.iterparse(filename, tag="vehicle")
# create a dataframe from XML data a single call
coords = ['x', 'y']
entries = ['CO2', 'CO', 'NOx', 'PMx']
df = pd.DataFrame(
extract_attributes(context, coords + entries),
columns=coords + entries, dtype=np.float)
# convert *all coordinates together*, remove the x, y columns
# note that the net.convertXY2LonLat() call *alters the
# numpy arrays in-place* so we don’t want to keep them anyway.
df['lng'], df['lat'] = net.convertXY2LonLat(df.x.to_numpy(), df.y.to_numpy())
df.drop(coords, axis=1, inplace=True)
# 'group' data by rounding the latitude and longitude
# effectively creating areas of 1/10000th degrees per side
lnglat = ['lng', 'lat']
df[lnglat] = df[lnglat].round(4)
# aggregate the results and return summed dataframe
return df.groupby(lnglat)[entries].sum()
emissions = parse_emissions("/path/to/emission_output.xml")
print(emissions)
使用 Pandas、一个示例 sumo 网络定义文件和一个重构的 XML 文件,通过重复您的 2 个示例时间步长条目 5900 次,我可以在大约 1 秒(总时间)内解析整个数据集。但是,我怀疑您的 11800 次集数太低(因为它小于 10MB XML 数据),所以我将 11800 * 20 == 236000 次样本写入文件,并且使用 Pandas 处理需要 22 秒。
您还可以查看GeoPandas,它可以让您按地理区域进行汇总。
添加回答
举报