Изменить форму массива в массиве в массиве

#python #numpy #dataframe #uproot #awkward-array

#python #numpy #фрейм данных #выкорчевать #неудобно-массив

Вопрос:

У меня есть корневой файл, который я открываю с 2000 записями и переменным количеством вложенных элементов, и в каждом столбце есть другая переменная. Допустим, меня интересуют только 5 из них. Я хочу поместить их в массив с np.shape(array)=(2000,250,5) помощью . 250 достаточно, чтобы содержать все вложенные элементы для каждой записи.

Корневой файл преобразуется в словарь путем удаления ДАННЫХ =[имя_переменной:[массив записей [массив вложенных элементов]]

Я создаю массив np.zeros(2000,250,5) и заполняю его нужными мне данными, но это занимает около 500 мс, и мне нужно решение, которое масштабируется, поскольку я нацелен на 1 миллион записей позже. Я нашел несколько решений, но мой самый низкий был около 300 мс

 lim_i=len(N_DATA["nTrack"])
i=0
INPUT_ARRAY=np.zeros((lim_i,500,5))
for l in range(len(INPUT_ARRAY)):
    while i < lim_i:
        EVENT=np.zeros((500,5))
        k=0
        lim_k=len(TRACK_DATA["Track_pt"][i])
        while k<lim_k:
            EVENT[k][0]=TRACK_DATA["Track_pt"][i][k]
            EVENT[k][1]=TRACK_DATA["Track_phi"][i][k]
            EVENT[k][2]=TRACK_DATA["Track_eta"][i][k]
            EVENT[k][3]=TRACK_DATA["Track_dxy"][i][k]
            EVENT[k][4]=TRACK_DATA["Track_charge"][i][k]
            k =1
        INPUT_ARRAY[i]=EVENT
        i =1
INPUT_ARRAY
  

Ответ №1:

Принимая во внимание второй комментарий fKarl Knechtel: «Вам следует избегать явного перебора массивов Numpy самостоятельно (практически гарантированно будет встроенная вещь Numpy, которая просто делает то, что вы хотите, и, вероятно, намного быстрее, чем может родной Python)», есть способ сделать это с помощью array-одновременное программирование, но не в NumPy. Причина, по которой выкорчевывание возвращает неудобные массивы, заключается в том, что вам нужен способ эффективно обрабатывать данные переменной длины.

У меня нет вашего файла, но я начну с похожего:

 >>> import uproot4
>>> import skhep_testdata
>>> events = uproot4.open(skhep_testdata.data_path("uproot-HZZ.root"))["events"]
  

Ветви, начинающиеся с "Muon_" в этом файле, имеют ту же структуру переменной длины, что и в ваших треках. (Имя типа C — это массив динамического размера, интерпретируемый в Python «как неровный».)

 >>> events.show(filter_name="Muon_*")
name                 | typename                 | interpretation                
--------------------- -------------------------- -------------------------------
Muon_Px              | float[]                  | AsJagged(AsDtype('>f4'))
Muon_Py              | float[]                  | AsJagged(AsDtype('>f4'))
Muon_Pz              | float[]                  | AsJagged(AsDtype('>f4'))
Muon_E               | float[]                  | AsJagged(AsDtype('>f4'))
Muon_Charge          | int32_t[]                | AsJagged(AsDtype('>i4'))
Muon_Iso             | float[]                  | AsJagged(AsDtype('>f4'))
  

Если вы просто запрашиваете эти массивы, вы получаете их как неудобный массив.

 >>> muons = events.arrays(filter_name="Muon_*")
>>> muons
<Array [{Muon_Px: [-52.9, 37.7, ... 0]}] type='2421 * {"Muon_Px": var * float32,...'>
  

Чтобы использовать их лучше, давайте импортируем неудобный массив и начнем с запроса его типа.

 >>> import awkward1 as ak
>>> ak.type(muons)
2421 * {"Muon_Px": var * float32, "Muon_Py": var * float32, "Muon_Pz": var * float32, "Muon_E": var * float32, "Muon_Charge": var * int32, "Muon_Iso": var * float32}
  

Что это значит? Это означает, что у вас есть 2421 запись с именами полей "Muon_Px" и т. Д., Каждая Из Которых содержит списки переменной длины float32 или int32 , в зависимости от поля. Мы можем посмотреть на один из них, преобразовав его в списки и дикты Python.

 >>> muons[0].tolist()
{'Muon_Px': [-52.89945602416992, 37.7377815246582],
 'Muon_Py': [-11.654671669006348, 0.6934735774993896],
 'Muon_Pz': [-8.16079330444336, -11.307581901550293],
 'Muon_E': [54.77949905395508, 39.401695251464844],
 'Muon_Charge': [1, -1],
 'Muon_Iso': [4.200153350830078, 2.1510612964630127]}
  

(Вы могли бы создать эти списки записей, а не записей списков, перейдя how="zip" к TTree.arrays или используя ak.unzip и ak.zip в неудобном массиве, но это связано с дополнением, которое вы хотите сделать.)

Проблема в том, что списки имеют разную длину. NumPy не имеет никаких функций, которые помогут нам здесь, потому что он полностью работает с прямолинейными массивами. Поэтому нам нужна функция, специфичная для неудобного массива, ak.num.

 >>> ak.num(muons)
<Array [{Muon_Px: 2, ... Muon_Iso: 1}] type='2421 * {"Muon_Px": int64, "Muon_Py"...'>
  

Это говорит нам о количестве элементов в каждом списке для каждого поля. Для наглядности посмотрите на первый:

 >>> ak.num(muons)[0].tolist()
{'Muon_Px': 2, 'Muon_Py': 2, 'Muon_Pz': 2, 'Muon_E': 2, 'Muon_Charge': 2, 'Muon_Iso': 2}
  

Вы хотите превратить эти нерегулярные списки в обычные списки, которые имеют одинаковый размер. Это называется «заполнением». Опять же, для этого есть функция, но сначала нам нужно получить максимальное количество элементов, чтобы мы знали, на сколько его дополнить.

 >>> ak.max(ak.num(muons))
4
  

Итак, давайте сделаем их все длиной 4.

 >>> ak.pad_none(muons, ak.max(ak.num(muons)))
<Array [{Muon_Px: [-52.9, 37.7, ... None]}] type='2421 * {"Muon_Px": var * ?floa...'>
  

Опять же, давайте посмотрим на первый, чтобы понять, что у нас есть.

 {'Muon_Px': [-52.89945602416992, 37.7377815246582, None, None],
 'Muon_Py': [-11.654671669006348, 0.6934735774993896, None, None],
 'Muon_Pz': [-8.16079330444336, -11.307581901550293, None, None],
 'Muon_E': [54.77949905395508, 39.401695251464844, None, None],
 'Muon_Charge': [1, -1, None, None],
 'Muon_Iso': [4.200153350830078, 2.1510612964630127, None, None]}
  

Вы хотели дополнить их нулями, а не None , поэтому мы преобразуем недостающие значения в нули.

 >>> ak.fill_none(ak.pad_none(muons, ak.max(ak.num(muons))), 0)[0].tolist()
{'Muon_Px': [-52.89945602416992, 37.7377815246582, 0.0, 0.0],
 'Muon_Py': [-11.654671669006348, 0.6934735774993896, 0.0, 0.0],
 'Muon_Pz': [-8.16079330444336, -11.307581901550293, 0.0, 0.0],
 'Muon_E': [54.77949905395508, 39.401695251464844, 0.0, 0.0],
 'Muon_Charge': [1, -1, 0, 0],
 'Muon_Iso': [4.200153350830078, 2.1510612964630127, 0.0, 0.0]}
  

Наконец, в NumPy нет записей (кроме структурированного массива, что также подразумевает, что столбцы являются смежными в памяти; «записи» неудобного массива являются абстрактными). Итак, давайте разархивируем то, что у нас есть, в шесть отдельных массивов.

 >>> arrays = ak.unzip(ak.fill_none(ak.pad_none(muons, ak.max(ak.num(muons))), 0))
>>> arrays
(<Array [[-52.9, 37.7, 0, 0, ... 23.9, 0, 0, 0]] type='2421 * var * float64'>,
 <Array [[-11.7, 0.693, 0, 0, ... 0, 0, 0]] type='2421 * var * float64'>,
 <Array [[-8.16, -11.3, 0, 0, ... 0, 0, 0]] type='2421 * var * float64'>,
 <Array [[54.8, 39.4, 0, 0], ... 69.6, 0, 0, 0]] type='2421 * var * float64'>,
 <Array [[1, -1, 0, 0], ... [-1, 0, 0, 0]] type='2421 * var * int64'>,
 <Array [[4.2, 2.15, 0, 0], ... [0, 0, 0, 0]] type='2421 * var * float64'>)
  

Обратите внимание, что эта одна строка выполняет все, начиная с исходных данных — извлекает из Uproot ( muons ). Я не собираюсь сейчас его профилировать, но вы обнаружите, что эта одна строка значительно быстрее, чем явный цикл.

Теперь то, что у нас есть, семантически эквивалентно шести массивам NumPy, поэтому мы просто приведем их как NumPy. (Попытки сделать это с нерегулярными данными завершатся неудачей. Вы должны явно дополнить данные.)

 >>> numpy_arrays = [ak.to_numpy(x) for x in arrays]
>>> numpy_arrays
[array([[-52.89945602,  37.73778152,   0.        ,   0.        ],
        [ -0.81645936,   0.        ,   0.        ,   0.        ],
        [ 48.98783112,   0.82756668,   0.        ,   0.        ],
        ...,
        [-29.75678635,   0.        ,   0.        ,   0.        ],
        [  1.14186978,   0.        ,   0.        ,   0.        ],
        [ 23.9132061 ,   0.        ,   0.        ,   0.        ]]),
 array([[-11.65467167,   0.69347358,   0.        ,   0.        ],
        [-24.40425873,   0.        ,   0.        ,   0.        ],
        [-21.72313881,  29.8005085 ,   0.        ,   0.        ],
        ...,
        [-15.30385876,   0.        ,   0.        ,   0.        ],
        [ 63.60956955,   0.        ,   0.        ,   0.        ],
        [-35.66507721,   0.        ,   0.        ,   0.        ]]),
 array([[ -8.1607933 , -11.3075819 ,   0.        ,   0.        ],
        [ 20.19996834,   0.        ,   0.        ,   0.        ],
        [ 11.16828537,  36.96519089,   0.        ,   0.        ],
        ...,
        [-52.66374969,   0.        ,   0.        ,   0.        ],
        [162.17631531,   0.        ,   0.        ,   0.        ],
        [ 54.71943665,   0.        ,   0.        ,   0.        ]]),
 array([[ 54.77949905,  39.40169525,   0.        ,   0.        ],
        [ 31.69044495,   0.        ,   0.        ,   0.        ],
        [ 54.73978806,  47.48885727,   0.        ,   0.        ],
        ...,
        [ 62.39516068,   0.        ,   0.        ,   0.        ],
        [174.20863342,   0.        ,   0.        ,   0.        ],
        [ 69.55621338,   0.        ,   0.        ,   0.        ]]),
 array([[ 1, -1,  0,  0],
        [ 1,  0,  0,  0],
        [ 1, -1,  0,  0],
        ...,
        [-1,  0,  0,  0],
        [-1,  0,  0,  0],
        [-1,  0,  0,  0]]),
 array([[4.20015335, 2.1510613 , 0.        , 0.        ],
        [2.18804741, 0.        , 0.        , 0.        ],
        [1.41282165, 3.38350415, 0.        , 0.        ],
        ...,
        [3.76294518, 0.        , 0.        , 0.        ],
        [0.55081069, 0.        , 0.        , 0.        ],
        [0.        , 0.        , 0.        , 0.        ]])]
  

И теперь NumPy dstack подходит. (Это делает их смежными в памяти, поэтому вы можете использовать структурированные массивы NumPy, если хотите. Мне было бы проще отслеживать, какой индекс означает какую переменную, но это зависит от вас. На самом деле, Xarray особенно хорош для отслеживания метаданных прямолинейных массивов.)

 >>> import numpy as np
>>> np.dstack(numpy_arrays)
array([[[-52.89945602, -11.65467167,  -8.1607933 ,  54.77949905,
           1.        ,   4.20015335],
        [ 37.73778152,   0.69347358, -11.3075819 ,  39.40169525,
          -1.        ,   2.1510613 ],
        [  0.        ,   0.        ,   0.        ,   0.        ,
           0.        ,   0.        ],
        [  0.        ,   0.        ,   0.        ,   0.        ,
           0.        ,   0.        ]],

       [[ -0.81645936, -24.40425873,  20.19996834,  31.69044495,
           1.        ,   2.18804741],
        [  0.        ,   0.        ,   0.        ,   0.        ,
           0.        ,   0.        ],
        [  0.        ,   0.        ,   0.        ,   0.        ,
           0.        ,   0.        ],
        [  0.        ,   0.        ,   0.        ,   0.        ,
           0.        ,   0.        ]],

       [[ 48.98783112, -21.72313881,  11.16828537,  54.73978806,
           1.        ,   1.41282165],
        [  0.82756668,  29.8005085 ,  36.96519089,  47.48885727,
          -1.        ,   3.38350415],
        [  0.        ,   0.        ,   0.        ,   0.        ,
           0.        ,   0.        ],
        [  0.        ,   0.        ,   0.        ,   0.        ,
           0.        ,   0.        ]],

       ...,

       [[-29.75678635, -15.30385876, -52.66374969,  62.39516068,
          -1.        ,   3.76294518],
        [  0.        ,   0.        ,   0.        ,   0.        ,
           0.        ,   0.        ],
        [  0.        ,   0.        ,   0.        ,   0.        ,
           0.        ,   0.        ],
        [  0.        ,   0.        ,   0.        ,   0.        ,
           0.        ,   0.        ]],

       [[  1.14186978,  63.60956955, 162.17631531, 174.20863342,
          -1.        ,   0.55081069],
        [  0.        ,   0.        ,   0.        ,   0.        ,
           0.        ,   0.        ],
        [  0.        ,   0.        ,   0.        ,   0.        ,
           0.        ,   0.        ],
        [  0.        ,   0.        ,   0.        ,   0.        ,
           0.        ,   0.        ]],

       [[ 23.9132061 , -35.66507721,  54.71943665,  69.55621338,
          -1.        ,   0.        ],
        [  0.        ,   0.        ,   0.        ,   0.        ,
           0.        ,   0.        ],
        [  0.        ,   0.        ,   0.        ,   0.        ,
           0.        ,   0.        ],
        [  0.        ,   0.        ,   0.        ,   0.        ,
           0.        ,   0.        ]]])
  

Комментарии:

1. Спасибо, это очень полезно, поскольку вы проходите аналогичный пример с моей проблемой. Я хотел бы спросить вас, как я мог бы масштабировать этот скрипт по вашему выбору: ‘>>> ak.max(ak.num (мюоны))’ ‘4’ В последующие дни мне приходится извлекать несколько корневых файлов. Я думаю, что довольно неэффективно перебирать все файлы, чтобы добраться до реального «максимума» всех файлов. Достаточно ли высокой оценки или это будет слишком дорого с точки зрения памяти?

2. Чтобы ответить на этот вопрос, вы должны спросить, для чего вы заполняете массивы. Если это связано с тем, что алгоритмы машинного обучения обычно используют векторы фиксированной ширины в качестве входных данных, выбор будет зависеть не столько от количества частиц в наборе данных, сколько от свойств алгоритма машинного обучения. Если вы не хотите потерять какие-либо частицы, вам, вероятно, не следует заполнять массивы, а выполнять анализ непосредственно на нерегулярных данных.

Ответ №2:

Наблюдение 1: мы можем назначить непосредственно соответствующим подмассивам INPUT_ARRAY[i] вместо того, чтобы создавать EVENT в качестве прокси для INPUT_ARRAY[i] , а затем копировать его. (Я также задам имена ваших переменных в нижнем регистре, чтобы следовать обычным соглашениям.

 lim_i = len(n_data["nTrack"])
i = 0
input_array = np.zeros((lim_i,500,5))
for l in range(len(input_array)):
    while i < lim_i:
        k = 0
        lim_k = len(track_data["Track_pt"][i])
        while k < lim_k:
            input_array[i][k][0] = track_data["Track_pt"][i][k]
            input_array[i][k][1] = track_data["Track_phi"][i][k]
            input_array[i][k][2] = track_data["Track_eta"][i][k]
            input_array[i][k][3] = track_data["Track_dxy"][i][k]
            input_array[i][k][4] = track_data["Track_charge"][i][k]
            k  = 1
        i  = 1
  

Наблюдение 2. назначения, которые мы выполняем во внутреннем цикле, имеют одну и ту же базовую структуру. Было бы неплохо, если бы мы могли взять различные записи TRACK_DATA dict (которые являются 2-мерными данными) и сложить их вместе. Numpy имеет удобный (и эффективный) встроенный для укладки 2-мерных данных по третьему измерению : np.dstack . Подготовив этот 3-мерный массив, мы можем просто скопировать из него механически:

 track_array = np.dstack((
    track_data['Track_pt'],
    track_data['Track_phi'],
    track_data['Track_eta'],
    track_data['Track_dxy'],
    track_data['Track_charge']
))
lim_i = len(n_data["nTrack"])
i = 0
input_array = np.zeros((lim_i,500,5))
for l in range(len(input_array)):
    while i < lim_i:
        k = 0
        lim_k = len(track_data["Track_pt"][i])
        while k < lim_k:
            input_array[i][k][0] = track_data[i][k][0]
            input_array[i][k][1] = track_data[i][k][1]
            input_array[i][k][2] = track_data[i][k][2]
            input_array[i][k][3] = track_data[i][k][3]
            input_array[i][k][4] = track_data[i][k][4]
            k  = 1
        i  = 1
  

Наблюдение 3: но теперь цель нашего самого внутреннего цикла — просто скопировать весь фрагмент track_data вдоль последнего измерения. Мы могли бы просто сделать это напрямую:

 track_array = np.dstack((
    track_data['Track_pt'],
    track_data['Track_phi'],
    track_data['Track_eta'],
    track_data['Track_dxy'],
    track_data['Track_charge']
))
lim_i = len(n_data["nTrack"])
i = 0
input_array = np.zeros((lim_i,500,5))
for l in range(len(input_array)):
    while i < lim_i:
        k = 0
        lim_k = len(track_data["Track_pt"][i])
        while k < lim_k:
            input_array[i][k] = track_data[i][k]
            k  = 1
        i  = 1
  

Наблюдение 4: Но на самом деле, те же рассуждения применимы к двум другим измерениям массива. Очевидно, что наше намерение состоит в том, чтобы скопировать весь массив, созданный из dstack ; и это уже новый массив, поэтому мы могли бы просто использовать его напрямую.

 input_array = np.dstack((
    track_data['Track_pt'],
    track_data['Track_phi'],
    track_data['Track_eta'],
    track_data['Track_dxy'],
    track_data['Track_charge']
))
  

Комментарии:

1. Может случиться так, что вам потребуется явно преобразовать значения из track_data dict в массивы Numpy. Я никогда не слышал об awkward-array uproot инструментах or, которые вы используете, поэтому я не знаю, что они здесь будут делать. Я просто рассказываю о ключевой идее, которая заключается в следующем: используйте инструменты Numpy для управления данными Numpy.

2. Но, кроме того, хотя вам следует избегать явного перебора массивов Numpy самостоятельно (практически гарантированно будет встроенная функция Numpy, которая просто делает то, что вы хотите, и, вероятно, намного быстрее, чем может родной Python), было бы неплохо воспользоваться этой возможностью, чтобы научиться выполнять циклкак родной .

3. np.dstack, похоже, является здесь правильным инструментом, но я не смог следовать вашим наблюдениям (я думаю, из-за отсутствия моих навыков python): » для i в диапазоне (eventrange): track_array = np.dstack(( track_data[‘Track_pt’][i], track_data[‘Track_phi’][i], track_data[‘Track_eta’][i], track_data[‘Track_dxy’][i], track_data[‘Track_charge’][i])) input_array[i]=track_array track_array» Вот где я сейчас нахожусь. для пояснения это касается физики элементарных частиц. дорожки — это измеренные частицы, и я хочу, чтобы их переменные были в одном объекте. но есть

4. несколько событий в одном файле. поэтому я хочу, чтобы объекты / дорожки были в отдельных массивах. количество дорожек является переменным. Данные должны стать входными данными для нейронной сети, поэтому я хочу, чтобы размерность для каждого события была одинаковой, и если дорожек меньше 250, их значения останутся 0. код, который у меня есть, помещает дорожки в объекты, но не сортируется по событию. моя единственная идея — перебирать их, но это на 100% неправильная идея.

5. Я думаю, вам следует задать новый вопрос.