Обнаружение нарушений в структуре

Обнаружение нарушений в структуре

В тесте на ускорение нажатий требовалось многократно повторить одну и ту же комбинацию нажатий на две разных кнопки.

u = 'd/m__178.155.4.92__3219165950000190320.tsv'
M = pd.read_table(u)
M.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1041 entries, 0 to 1040
Data columns (total 2 columns):
 #   Column  Non-Null Count  Dtype  
---  ------  --------------  -----  
 0   t       1041 non-null   float64
 1   v       1041 non-null   int64  
dtypes: float64(1), int64(1)
memory usage: 16.4 KB

Самыми сложными были 5-й и 6-й этапы теста, где на 3 нажатия одной рукой приходилось 1 нажатие другой.

coab, coad = 1005, 13
tab = M.t[M.v==coab].iloc[0]
tad = M.t[(M.t>tab) & (M.v==coad)].iloc[0]
R = M[(M.t>=tab)&(M.t<tad)].copy()
# оставим только нажатия клавиши - код < 100
R = R[R.v.abs()<100]
R = R[R.v > 0]
R.plot('t','v');
_images/i_seq_4_0.png

По заданию должно быть только два типа нажатий: на клавиши с кодами 70 и 74 (F и J). Последовательность кодов можно преобразовать в строку. Со строками можно производить операции поиска.

rr = R.v[R.v.isin([70,74])].values

s=''.join(map(chr, rr))
seq = s.lower()
seq
'jfffjfffjfffjfffjfffjfffjfffjffjfffjfffjfffjfffjfffjfffjfffjfffjfff'

Самый простой подход - пройти вдоль строки и найти все места, где она совпадает с образцом.

pattern = 'jfff'
ii=[]
i = 0
while i < len(seq)-len(pattern)+1:
    i = seq.find(pattern, i)
    if i<0:
        break
    ii.append(i)
    i += len(pattern)

ii = asarray(ii)
ii
array([ 0,  4,  8, 12, 16, 20, 24, 31, 35, 39, 43, 47, 51, 55, 59, 63])

Можно записать цикл в одну строку.

ii = array([m.start() for m in re.finditer(pattern, seq)])
ii
array([ 0,  4,  8, 12, 16, 20, 24, 31, 35, 39, 43, 47, 51, 55, 59, 63])

Совпадения с образцом должны происходить строго через каждые 4 символа.

Ошибки можно установить по тем местам, где это не так. Для этого найдем места, где разница между обнаруженными «правильными» индексами не равна длине паттерна и сдвинемся на длину паттерна.

errpos = ii[where(diff(ii)!=len(pattern))[0]] + len(pattern)
errpos
array([28])
tterr = R.t.iloc[errpos]
tterr
720    254.654
Name: t, dtype: float64

Посмотрим исходные маркеры в том месте, где случилась ошибка.

i = R.index[errpos[0]]
M[i:i+12]
t v
720 254.654 74
721 254.768 -74
722 254.954 70
723 255.053 -70
724 255.163 70
725 255.258 -70
726 255.485 74
727 255.569 -74
728 256.527 70
729 256.647 -70
730 256.811 70
731 256.944 -70

В чем суть обнаруженной ошибки? Сколько было нажатий левой рукой между нажатиями правой?

Знание мест, где начинались «правильные» паттерны, позволит нам выделить четыре типа реакций и проанализировать их отдельно. Например, можно изобразить их разными цветами на рисунке. Для подписей мы сделаем последовательность с обозначением рук.

haha = where(asarray(list(pattern))=='j', 'R', 'L')
haha
array(['R', 'L', 'L', 'L'], dtype='<U1')
[ii.reshape(-1,1) + arange(len(pattern))]
[array([[ 0,  1,  2,  3],
        [ 4,  5,  6,  7],
        [ 8,  9, 10, 11],
        [12, 13, 14, 15],
        [16, 17, 18, 19],
        [20, 21, 22, 23],
        [24, 25, 26, 27],
        [31, 32, 33, 34],
        [35, 36, 37, 38],
        [39, 40, 41, 42],
        [43, 44, 45, 46],
        [47, 48, 49, 50],
        [51, 52, 53, 54],
        [55, 56, 57, 58],
        [59, 60, 61, 62],
        [63, 64, 65, 66]])]
R['iri'] = R.t.diff()

I = ii.reshape(-1,1) + arange(len(pattern))
T = R.t.values[I]
V = R.iri.values[I]

plt.figure(figsize=(10,2))

plot(T, V, 'o', alpha=.6); legend(haha);

yerr = .9
plot(tterr, yerr*ones_like(tterr), 'x', ms=10, mew=3, color='k', alpha=.5)
ylim(0, 1.2);
title('Паттерн {}'.format(pattern));
_images/i_seq_20_0.png

Можно заметить, что интервалы между нажатиями систематически варьировали в зависимости от позиции в паттерне.

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

Последовательность чисел

С последовательностями можно работать напрямую - в виде чисел.

Для наглядности преобразуем их в нули и единицы. Это один из способов стандартизации чисел - отнять минимум и разделить на размах.

rr = (rr - rr.min())/(rr.max()-rr.min())
plot(rr);
_images/i_seq_24_0.png

Из чисел, состоящих из одного символа, также можно сделать строки.

seq = ''.join(rr.astype(int).astype(str))
seq
'1000100010001000100010001000100100010001000100010001000100010001000'

При работе с числами совпадение последовательностей соответствует корреляции.

patt = array([1,0,0,0])
c = correlate(rr, patt)
plot(c);
_images/i_seq_28_0.png
where(c==c.max())[0]
array([ 0,  4,  8, 12, 16, 20, 24, 28, 31, 35, 39, 43, 47, 51, 55, 59, 63],
      dtype=int64)

Сравните полученные индексы, в чем разница с теми, что мы получили поиском подстроки?

c = correlate(rr, 1-patt)
plot(c);
_images/i_seq_31_0.png
where(c==c.min())[0]
array([ 0,  4,  8, 12, 16, 20, 24, 31, 35, 39, 43, 47, 51, 55, 59, 63],
      dtype=int64)

А теперь индексы различаются?

Корреляционные методы работают и при частичном совпадении «подъемов» и «спусков». Например, можно посчитать две функции кросс-корреляции с обратными паттернами и выбрать тот, при котором события разделены.

Чтобы найти точное соответствие подобно тому как мы искали подстроки, можно составить матрицу из одной и той же последовательности, но со сдвигом. Затем найти строки, совпадающие с паттерном.

Seq = np.vstack(np.roll(rr, shift) for shift in -np.arange(len(patt))).T

where(all(Seq == patt, axis=1))[0]
array([ 0,  4,  8, 12, 16, 20, 24, 31, 35, 39, 43, 47, 51, 55, 59, 63],
      dtype=int64)

Таким образом, при работе с последовательностями большинство операций производится с индексами - позициями тех или иных событий. Расстояние между событиями - разница между индексами.

В рассмотренном нами случае каждое событие имело также отметку времени. Поэтому наряду с разницей в индексах можно рассчитать интервалы в секундах.

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