#python #numpy
Вопрос:
У меня есть массив как таковой:
myarray = [['a', 'b', 'c'],
['b', 'c', 'd'],
['c', 'd', 'e']]
И для этого np.unique(myarray, return_counts=True)
работает потрясающе и дает мне желаемый результат. Однако затем я хотел бы применить его строка за строкой, и чтобы он мог сообщить мне, что в строке № 1 значения d и e равны 0.
На данный момент я пытался добавлять их в строку массива каждую итерацию во время цикла for, а затем вычитать 1 для каждого счета, но даже это меня смущает. Я пробовал эти два решения:
for i in range(mylen):
unique, counts = np.unique(np.array([list(myarray[i]), 'a', 'b', 'c', 'd', 'e']), return_counts=True) # attempt 1
unique, counts = np.unique(np.vstack((myarray[i], 'a', 'b', 'c', 'd', 'e')), return_counts=True) # attempt 2
Но ни то, ни другое не работает. У кого-нибудь есть элегантное решение? Это будет использоваться для тысяч, возможно, миллионов значений, поэтому время вычисления имеет некоторое отношение к обсуждению.
Ответ №1:
Вы можете использовать np.unique
с return_inverse=True
, чтобы получить то, что вы хотите:
letters, inv = np.unique(myarray, return_inverse=True)
inv = inv.reshape(myarray.shape)
inv
это сейчас
array([[0, 1, 2],
[1, 2, 3],
[2, 3, 4]], dtype=int64)
Вы можете получить количество всех уникальных элементов в одной строке:
>>> (inv == np.arange(len(letters)).reshape(-1, 1, 1)).sum(-1)
array([[1, 0, 0],
[1, 1, 0],
[1, 1, 1],
[0, 1, 1],
[0, 0, 1]])
Первое измерение соответствует букве в letters
, второе-номеру строки, так sum(-1)
как суммы по столбцам. Вы можете получить подсчеты для столбцов, используя sum(1)
вместо этого. В вашем симметричном примере результат будет идентичным.
Никакого зацикливания, никакого np.apply_along_axis
(что является прославленным циклом), все векторизовано. Вот краткий временной тест:
np.random.seed(42)
myarray = np.random.choice(list(string.ascii_lowercase), size=(100, 100))
def Epsi95(arr):
uniques = np.unique(arr)
def fun(x):
base_dict = dict(zip(uniques, [0]*uniques.shape[0]))
base_dict.update(dict(zip(*np.unique(x, return_counts=True))))
return [i[-1] for i in sorted(base_dict.items())]
return np.apply_along_axis(fun, 1, arr)
def MadPhysicist(myarray):
letters, inv = np.unique(myarray, return_inverse=True)
inv = inv.reshape(myarray.shape)
return (inv == np.arange(len(letters)).reshape(-1, 1, 1)).sum(-1)
%timeit Epsi95(myarray)
6.37 ms ± 26.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%timeit MadPhysicist(myarray)
1.28 ms ± 6.85 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Комментарии:
1. Да, это решение намного превосходит по эффективности.
2. Это выглядит фантастически, спасибо. Просто чтобы мне было ясно по нескольким пунктам: 1) массив, который вы показываете после
(inv == np.arange(len(letters)).reshape(-1, 1, 1)).sum(-1)
, предназначен для каждой возможности в моей вселенной, количество раз, когда он появляется в каждой строке, верно? Это[1,0,0]
означает, что он появляется один раз в первой строке, а затем вообще не в строках 2 и 3. 2) Если бы я хотел применить это к столбцам вместо строк, как бы это работало? Было бы проще всего просто перенести myarray и применить ту же функцию?3. Вы можете играть с размерами да. И да, к интерпретации данных.
Ответ №2:
myarray = [['a', 'b', 'c'],
['b', 'c', 'd'],
['c', 'd', 'e']]
arr = np.array(myarray)
uniques = np.unique(arr)
def fun(x):
base_dict = dict(zip(uniques, [0]*uniques.shape[0]))
base_dict.update(dict(zip(*np.unique(x, return_counts=True))))
return [i[-1] for i in sorted(base_dict.items())]
np.apply_along_axis(fun, 1, arr)
# array([[1, 1, 1, 0, 0], # a=1 b=1 c=1 d=0 e=0
# [0, 1, 1, 1, 0],
# [0, 0, 1, 1, 1]], dtype=int64)
Комментарии:
1.
apply_along_axis
это просто прославленныйfor
цикл, что бы вам ни говорили документы.2. Вам никогда не нужно сортировать выходные данные
unique
3. вы правы (оба случая), во втором случае на самом деле я занимался
return list(base_dict.values())
, так как дикт 3.6 поддерживает порядок, но позже подумал обобщить и забыл удалить первый сорт.
Ответ №3:
Вы можете выполнить итерацию по строкам списка, а затем по уникальным значениям всего набора. Приведу пример ниже, и его можно использовать для вставки элементов в словарь или любую другую структуру по вашему выбору.
Пример:
import numpy as np
myarray = [['a', 'b', 'c'],
['b', 'c', 'd'],
['c', 'd', 'e']]
uniq = np.unique(np.array(myarray))
for idx, row in enumerate(myarray):
for x in uniq:
print(f"Row {idx} Element ({x}) Count: {row.count(x)}")
Выход:
Row 0 Element (a) Count: 1
Row 0 Element (b) Count: 1
Row 0 Element (c) Count: 1
Row 0 Element (d) Count: 0
Row 0 Element (e) Count: 0
Row 1 Element (a) Count: 0
Row 1 Element (b) Count: 1
Row 1 Element (c) Count: 1
Row 1 Element (d) Count: 1
Row 1 Element (e) Count: 0
Row 2 Element (a) Count: 0
Row 2 Element (b) Count: 0
Row 2 Element (c) Count: 1
Row 2 Element (d) Count: 1
Row 2 Element (e) Count: 1
Чтобы использовать список словарей для каждой строки:
import numpy as np
myarray = [['a', 'b', 'c'],
['b', 'c', 'd'],
['c', 'd', 'e']]
uniq = np.unique(np.array(myarray))
row_vals = []
for idx, row in enumerate(myarray):
dict = {}
for x in uniq:
dict[x] = row.count(x)
row_vals.append(dict)
for r in row_vals:
print(r)
Выход:
{'a': 1, 'b': 1, 'c': 1, 'd': 0, 'e': 0}
{'a': 0, 'b': 1, 'c': 1, 'd': 1, 'e': 0}
{'a': 0, 'b': 0, 'c': 1, 'd': 1, 'e': 1}