Ваш код работает медленно по 2 причинам:
Вы выполняете ненужную работу и используете неэффективные операторы Python. Вы не используете veh_id
, но все еще используете int()
для его преобразования. Вы создаете пустой словарь только для установки в нем 4 ключей в отдельных операторах, вы используете отдельные вызовы str()
и round()
вместе с конкатенацией строк, где форматирование строк может выполнять всю эту работу за один шаг, вы постоянно ссылаетесь на .attrib
,поэтому Python должен постоянно находить этот словарный атрибут для вас.
Реализация sumolib.net.convertXY2LonLat()
очень неэффективна, когда используется для каждого отдельного человека (x, y)координат;он загружает смещение и 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 / 10000th градусов долготы и широты, то вы можете сделать преобразование из (x, y) пар в пары long / lat намного быстрее, если вместо этого будете кормить целые массивы numpy до net.convertXY2LonLat()
. Это связано с тем, что pyproj.Proj()
принимает массивы для преобразования координат в большом количестве , экономя значительное количество времени, избегая сотен тысяч отдельных вызовов преобразования, вместо этого мы сделали бы всего один вызов,
Вместо того, чтобы обрабатывать это с помощью словаря Python и объектов с плавающей запятой, вам действительно следует использовать DataFrame Pandas здесь. Он может тривиально принимать строки, взятые из каждого словаря атрибутов элемента (используя 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, образец файла определения сумо-сети и восстановленный XML-файл, повторяя ваши 2 образца записей временного шага 5900 раз, я могу проанализировать весь набор данных примерно за1 секунда, общее время. Тем не менее, я подозреваю, что число ваших 11800 временных наборов слишком мало (так как это менее 10 МБ данных XML), поэтому я записал 11800 * 20 == 236000 раз ваш образец в файл, и это заняло 22 секунды для обработки с Pandas.
Вы также можете посмотреть на GeoPandas , который позволит вам агрегировать по географическим областям .