Pandas dataframe: из информации о времени изменения состояния получить, когда и когда состояние длилось - PullRequest
1 голос
/ 10 июля 2020

Обратите внимание, что я не знаю правильного имени для операции, которую хочу выполнить, и поэтому я попытаюсь объяснить на примере:

У меня есть такой фрейм данных:

sample_sf = pd.DataFrame({
    'time':[101, 104, 112, 120, 120, 134, 202, 215, 222, 255, 258, 272, 290, 294, 294, 305, 305, 307, 504, 520, 527, 538, 557, 557], 
    'status':[1,1,0,0,1,0,1,0,1,0,1,0,1,1,1,0,0,0,1,1,0,1,0,0],
    'opponent_id':[1, 2, 1, 2, 3, 3, 2,2,2,2,4,4,2,3, 5, 3, 5, 2, 6,3, 6, 6, 3,6]})

Это отслеживает, видит ли меня оппонент или нет. Status = 1 указывает, что противник с данным идентификатором нашел меня, тогда как status = 0 указывает, что данный противник потерял меня. Следовательно, первая строка моего фрейма данных указывает, что противник номер 1 нашел меня в момент времени 101. Вторая строка означает, что 2-й противник нашел меня в момент времени 104, третья строка означает, что противник 1 потерял меня в момент времени 112, а 4-й раунд означает, что противник номер 2 потерял меня в момент 120.

Когда ни один противник меня не видит, я невидим (состояние 0), и когда хотя бы один противник видит меня, я видим (состояние 1). Я хотел бы выяснить, когда и когда я был невидимым, а когда и когда стал видимым. Где последний наблюдаемый статус должен длиться до конца игры (время = 900). Поэтому мне нужен следующий фрейм данных:

out = pd.DataFrame({'from':[0, 101, 134, 202, 215, 222,255, 258, 272, 290, 307,504,557], 'to':[100, 133, 201, 214, 221, 254, 257, 271,289,306,503,556,900], 'status':[0, 1, 0, 1, 0, 1,0,1,0, 1,9,1,0]})

, который выглядит так:

from    to  status
0   0   100 0
1   101 133 1
2   134 201 0
3   202 214 1
4   215 221 0
5   222 254 1
6   255 257 0
7   258 271 1
8   272 289 0
9   290 306 1
10  307 503 9
11  504 556 1
12  557 900 0

Ответы [ 3 ]

2 голосов
/ 11 июля 2020

Вот решение, которое не использует никаких for циклов. Я считаю, что пока это должно быть быстрее, чем другие предложения (вам лучше провести тест). Он принимает во внимание, что «оппонент_ид», по сути, не имеет отношения к делу (см. Мое примечание в конце). вывод:

    from   to  status
0      0  100       0
1    101  133       1
5    134  201       0
6    202  214       1
7    215  221       0
8    222  254       1
9    255  257       0
10   258  271       1
11   272  289       0
12   290  306       1
15   307  503       0
16   504  556       1
20   557  900       0

Пояснение:

  • Скопируйте sample_sf в out.
  • Отбросьте столбец «оппонент_ид». Нам это не нужно.
  • Замените все 0 в столбце status на -1.
  • Используйте numpy s np.cumsum(), чтобы записать совокупную сумму в 'status' ( это сумма всех чисел до определенной позиции.Теперь в столбце «статус» у нас есть количество противников, которые могут видеть вас каждый раз.
  • Отбросьте все строки с равным временем, оставив только последний. Причина этого: если в определенный момент времени происходит несколько событий, мы заботимся только об окончательном количестве противников, наблюдающих за вами.
  • Добавить время 0 к статусу 0 (вы начинаете невидимость) и сбросить индексирование.
  • Извлечь в ind индексы строк со статусом 0 (то есть когда вы становитесь невидимым).
  • Добавьте в этот список индексов также индексы строк, когда вы становитесь видимыми (то есть следующий индекс после каждого статус 0). Сортировка индексов. Теперь в ind у нас есть строки, когда ваше состояние меняется с видимого на невидимое или наоборот.
  • Сохранение последнего статуса в last_status. Если мы оказались невидимыми, то мы должен сбрасывать p последний индекс в ind, потому что у нас нет последнего перехода из невидимого состояния в видимое.
  • Оставьте только строки в ind.
  • Переименуйте столбец 'time' в ' from 'и добавить столбец' to ', который аналогичен столбцу' from ', но уменьшен на единицу и сдвинут на единицу вверх (последняя запись заполняется цифрой 900).

Примечание:

Это решение работает только в случае, когда оппонент не может, следовательно, проиграть / найти вас два раза подряд, т.е. такой ввод запрещен:

    time  status  opponent_id
0    101       1            1
1    104       1            2
2    112       1            1

(противник 1 находил вас два раза подряд, не теряя). В вашем примере это условие выполняется. Итак, я предположил это. Если это не так, подумайте над другими ответами.

0 голосов
/ 10 июля 2020

Здесь используется состояние для удержания количества противников со статусом = 1. Он сканирует данные, сохраняя состояние, и выдает новое изменение, когда количество противников, видящих «меня», изменяется с 0 на 1 или наоборот.

import pandas as pd


def calculate_visibility(data):
    # Prepare state
    nr_opponents_that_see_me = 0
    prev_change_time_point = 0
    nr_opponents_that_see_me_during_prev_change = 0

    # Scan visibility by maintaining state
    result = list()
    for time_point_idx, time_point in enumerate(data.time):
        # Update visibility counter
        nr_opponents_that_see_me += 1 if data.status[time_point_idx] else -1
        is_last_time_point = time_point_idx == len(data.time) - 1
        is_last_change_in_time_point = is_last_time_point or (data.time[time_point_idx + 1] != time_point)
        
        # Check if visibility changed
        if is_last_change_in_time_point:

            turned_invisible = nr_opponents_that_see_me == 0
            turned_visible = nr_opponents_that_see_me == 1 and nr_opponents_that_see_me_during_prev_change == 0
            if turned_invisible or turned_visible:
                result.append([prev_change_time_point, time_point - 1, 1 if turned_invisible else 0])
                prev_change_time_point = time_point

                # Update prev #opponents that see me
                nr_opponents_that_see_me_during_prev_change = nr_opponents_that_see_me

    result.append([time_point, 900, 0 if (nr_opponents_that_see_me == 0) else 1])
    return pd.DataFrame(result, columns=["from", "to", "status"])

sample_sf = pd.DataFrame({
    'time': [101, 104, 112, 120, 120, 134, 202, 215, 222, 255, 258, 272, 290, 294, 294, 305, 305, 307, 504, 520, 527, 538, 557, 557],
    'status': [1,1,0,0,1,0,1,0,1,0,1,0,1,1,1,0,0,0,1,1,0,1,0,0],
    'opponent_id': [1, 2, 1, 2, 3, 3, 2,2,2,2,4,4,2,3, 5, 3, 5, 2, 6,3, 6, 6, 3,6]})

print(calculate_visibility(sample_sf))

Это дает следующий результат:

    from   to  status
0      0  100       0
1    101  133       1
2    134  201       0
3    202  214       1
4    215  221       0
5    222  254       1
6    255  257       0
7    258  271       1
8    272  289       0
9    290  306       1
10   307  503       0
11   504  556       1
12   557  900       0
0 голосов
/ 10 июля 2020

Используйте это:

import pandas as pd

df = pd.DataFrame({
    'time':[101, 104, 112, 120, 120, 134, 202, 215, 222, 255, 258, 272, 290, 294, 294, 305, 305, 307, 504, 520, 527, 538, 557, 557],
    'status':[1,1,0,0,1,0,1,0,1,0,1,0,1,1,1,0,0,0,1,1,0,1,0,0],
    'opponent_id':[1, 2, 1, 2, 3, 3, 2,2,2,2,4,4,2,3, 5, 3, 5, 2, 6,3, 6, 6, 3,6]})

set_time = set()
for i in range(1,7):
    l1 = df[df['opponent_id']==i]['time'].tolist()
    l1.sort()
    #print(i, l1)
    for j, k in zip(l1[::2],l1[1::2]):
        #print(j,k)
        set_time |= set(range(j,k))

times = list(set_time)
times.sort()

l2 = [0, times[0]]
for i, j in zip(times, times[1:]):
    if j-i>1:
        l2 += [i,j]
l2 +=[times[-1], 900]

l3 = []
for n, tup in enumerate(zip(l2,l2[1:])):
    a, b = tup
    if n % 2:
        l3 += [[a,b, n%2]]
    else:
        l3 += [[a+1,b-1,n%2]]

df1 = pd.DataFrame(l3, columns=['from','to','status'])

# Manual override to adjust for end errors.
df1.at[0, 'from']=0
df1.at[df1.index.max(), 'to']=900

Пояснение:

  1. Express время, когда вы видимы противнику как набор . например, для оппонента 1 время, которое видно, равно set(range(101, 112)), аналогично для оппонента 2 это будет set(range(104,120)), set(range(202,215)), ...

  2. Возьмите объединение всех этих значений наборы (т.е. set_time). Набор удаляет дубли, как удобно! : D

  3. Преобразовать set_time в список (например, times), отсортировать его.

  4. Для l oop: for i, j in zip(times, times[1:]) применяется для построения списка (l2) случаев изменения статуса с невидимого на видимый и наоборот. В основном нас не интересуют последовательные значения в списке times.

  5. Добавьте 2 записи спереди и сзади l2 для корректировки конечных ошибок.

  6. Последний для l oop for n, tup in enumerate(zip(l2,l2[1:])) создает список времени и состояния для фрейма данных.

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