Фильтрация пустого структурированного массива на основе частичного соответствия списку
У меня есть дополнительный вопрос по одному, который я разместил здесь. В этом вопросе я попытался суммировать значения в массивном структурированном массиве на основе нескольких критериев, включая совпадения в списке. @ali_m дал успешный ответ на этот вопрос:
criteriaList = ("Zone1", "Zone2")
myArray = np.array([(1, 1, 1, u'Zone3', 9.223),
(2, 1, 0, u'Zone2', 17.589),
(3, 1, 1, u'Zone2', 26.95),
(4, 0, 1, u'Zone1', 19.367),
(5, 1, 1, u'Zone1', 4.395)],
dtype=[('ID', '<i4'), ('Flag1', '<i4'), ('Flag2', '<i4'), ('ZoneName', '<U5'),
('Value', '<f8')])
result = myArray[(myArray["Flag1"] == 1) & (myArray["Flag2"] == 1)
& np.in1d(myArray["ZoneName"], criteriaList)]["Value"].sum()
Это дает желаемый результат 31,345.
Теперь я пытаюсь выяснить, как это изменить, если в моем массиве подчеркнуты значения с разделителями, и я хочу включить строку в мою сумму, если есть частичное совпадение с critieriaList. В следующем числовом структурированном массиве третья строка содержит значения, разделенные подчеркиванием. В этом случае я хотел бы включить значение, потому что "Zone1" включен как часть значения "ZoneName":
myArray = np.array([(1, 1, 1, u'Zone3', 9.223),
(2, 1, 0, u'Zone2', 17.589),
(3, 1, 1, u'Zone1_Zone3', 26.95),
(4, 0, 1, u'Zone1', 19.367),
(5, 1, 1, u'Zone1', 4.395)],
dtype=[('ID', '<i4'), ('Flag1', '<i4'), ('Flag2', '<i4'), ('ZoneName', '<U10'),
('Value', '<f8')])
Я попытался разделить значения в массиве:
str(myArray["ZoneName"]).split('_')
но потом не могу понять, что с ними делать, не открывая цикл и не используя операторы if. Любая помощь будет высоко ценится. Благодарю.
Вот пример того, как к этому можно обратиться, используя цикл и операторы if. Это не функционально, как написано, но обрисовывает в общих чертах мой мыслительный процесс согласно комментарию hpualij. (Это также вызовет проблемы, если есть "Zone14", так как "Zone1" находится в "Zone14")
values = []
criteriaList = ("Zone1", "Zone2")
for criteria in criteriaList:
zones = myArray["ZoneName"]
for zone in zones:
if criteria in zone:
print ("criteria=" + criteria)
print ("zone=" + zone)
value = myArray[((myArray["Flag1"] == 1) & (myArray["Flag2"] == 1)
& (myArray["ZoneName"] == zone))]["Value"].sum
print(value)
result = sum(values)
2 ответа
Вот рабочее решение моего вопроса, но оно довольно медленное (реальный массив имеет>200000 записей и более 50 "зон"). Он суммирует значения из простого структурного массива на основе нескольких полей, где поле "Flag1" == 1 И поле "Flag2" == 1 и где есть хотя бы одно совпадение в поле "ZoneName" со списком имен в списке критериев. Он найдет совпадение, если поле "ZoneName" содержит хотя бы одно значение из списка критериев (например, "Zone1_Zone3"), он не будет удваивать счет, если поле "ZoneName" содержит несколько значений из списка критериев (например, "Zone1_Zone2").), и он не будет совпадать с частичными совпадениями в списке критериев (например, "Zone14"). Спасибо @hpaulj за помощь! Я приветствую любые дальнейшие комментарии о способах улучшения этого, особенно в отношении скорости обработки.
import numpy as np
import re
def main():
#sum values from the array if "Flag1"==1, "Flag2"==1, and "ZoneName" includes
#either "Zone1" or "Zone2"
myArray = np.array([(1, 1, 1, u'Zone3', 9.223),
(2, 1, 0, u'Zone2', 17.589),
(3, 1, 1, u'Zone1_Zone2', 26.95),
(4, 0, 1, u'Zone2', 19.367),
(5, 1, 1, u'Zone1_Zone3', 4.395),
(5, 1, 1, u'Zone15', 8.565),
(5, 1, 1, u'Zone2', 7.125),
(5, 1, 0, u'Zone1', 6.395)],
dtype=[('ID', '<i4'), ('Flag1', '<i4'), ('Flag2', '<i4'), ('ZoneName', '<U15'),
('Value', '<f8')])
doneList = [] #empty list to track which zones have been calc'd
values = [] #empty list to store values from loop
criteriaList = ("Zone1", "Zone2")
zones = myArray["ZoneName"]
for criteria in criteriaList:
for zone in zones:
#only calc if the "ZoneName" value from the array includes the criteria
#in the current loop and the record has not already been calc'd
if ((find_word(str(np.char.split(zone,'_')), criteria)) & (zone not in doneList)):
#the key element from previous question
value = myArray[((myArray["Flag1"] == 1) & (myArray["Flag2"] == 1))
& np.in1d(myArray["ZoneName"], zone)]["Value"].sum()
values.append(value)
doneList.append(zone) #Needed to that "Zone1_Zone2" is not double counted
result = sum(values)
print result
def find_word(text, search):
result = re.findall('\\b'+search+'\\b', text, flags=re.IGNORECASE)
if len(result)>0:
return True
else:
return False
if __name__ == '__main__':
main()
('ZoneName', '<U10')
должно быть ('ZoneName', '<U11')
включить окончательный "3".
Давайте сосредоточимся на одном поле; мы можем ссылаться на него с новой переменной:
In [321]: names=myArray['ZoneName']
In [322]: names
Out[322]:
array(['Zone3', 'Zone2', 'Zone1_Zone3', 'Zone1', 'Zone1'],
dtype='<U11')
np.char
имеет функции, которые применяют строковые методы к элементам массива. Давай попробуем split
:
In [323]: np.char.split(names,'_')
Out[323]: array([['Zone3'], ['Zone2'], ['Zone1', 'Zone3'], ['Zone1'], ['Zone1']], dtype=object)
Я не думаю, что это помогает. У нас еще есть итерация для поиска в списках.
In [324]: np.char.find(names,'Zone1')
Out[324]: array([-1, -1, 0, 0, 0])
In [325]: np.char.find(names,'Zone3')
Out[325]: array([ 0, -1, 6, -1, -1])
Это выглядит лучше Теперь у нас есть числовой массив; '-1' для элементов, у которых нет строки.
Функционально это так же, как
In [326]: np.array([astr.find('Zone1') for astr in names])
Out[326]: array([-1, -1, 0, 0, 0])
Я не уверен, сколько времени сэкономит char.find
есть. Думаю, мы могли бы проверить это.:)
Это поможет вам преодолеть узкое место?
In [328]: %timeit np.array([astr.find('Zone1') for astr in names])
100000 loops, best of 3: 10.2 µs per loop
In [329]: %timeit np.char.find(names,'Zone1')
10000 loops, best of 3: 21.4 µs per loop
np.char.find
на самом деле медленнее! В массиве (5000,) метод понимания все еще быстрее, хотя поле не так велико.
Другой вариант view
поле как 2 или более полей:
In [352]: nn = names.view([('1st','U5'),('dash','U1'),('2nd','U5')])
In [353]: nn
Out[353]:
array([('Zone3', '', ''), ('Zone2', '', ''), ('Zone1', '_', 'Zone3'),
('Zone1', '', ''), ('Zone1', '', '')],
dtype=[('1st', '<U5'), ('dash', '<U1'), ('2nd', '<U5')])
In [354]: nn['1st']=='Zone1'
Out[354]: array([False, False, True, True, True], dtype=bool)
In [355]: nn['2nd']=='Zone1'
Out[355]: array([False, False, False, False, False], dtype=bool)
In [356]: (nn['1st']=='Zone1')|(nn['2nd']=='Zone3')
Out[356]: array([False, False, True, True, True], dtype=bool)
In [357]: (nn['1st']=='Zone3')|(nn['2nd']=='Zone3')
Out[357]: array([ True, False, True, False, False], dtype=bool)
Это использует тот факт, что строка 11
символы могут рассматриваться как 3 более короткие строки.
Это намного быстрее, чем строковые операции - если вы можете использовать точное количество символов
In [358]: %%timeit
.....: nn = M.view([('1st','U5'),('dash','U1'),('2nd','U5')])
.....: (nn['1st']=='Zone1')|(nn['2nd']=='Zone1')
.....:
1000 loops, best of 3: 264 µs per loop
С более мощным re.match
- около 1/3 скорости find
,
In [368]: %timeit [re.match('Zone1',astr) is not None for astr in M]
100 loops, best of 3: 14.7 ms per loop
In [369]: %timeit np.array([astr.find('Zone1') for astr in M])>-1
100 loops, best of 3: 5.31 ms per loop
Ой, я хочу re.search
чтобы потом соответствовать в строке.
In [372]: [re.search('Zone3',astr) for astr in names]
Out[372]:
[<_sre.SRE_Match object; span=(0, 5), match='Zone3'>,
None,
<_sre.SRE_Match object; span=(6, 11), match='Zone3'>,
None,
None]
In [376]: %timeit [re.search('Zone1',astr) is not None for astr in M]
100 loops, best of 3: 11.1 ms per loop
Лучше чем match
и только половина скорости find
, Шаблон должен быть усовершенствован, чтобы различать Zone1_
а также Zone14
, но это легко с re
,