Панды: заполнение пропущенных значений временем возникновения события - PullRequest
0 голосов
/ 31 августа 2018

Я уже задавал подобный вопрос (см. Здесь) , но, к сожалению, это было недостаточно ясно, поэтому я решил, что лучше создать новый, например, с лучшим набором данных и новым объяснением желаемый результат - редактирование было бы действительно серьезным изменением. Итак, у меня есть следующий набор данных (он уже отсортирован по дате и игроку):

d = {'player': ['1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '2', '2', '2', '2', '2', '3', '3', '3', '3', '3', '3'],
'date': ['2018-01-01 00:17:01', '2018-01-01 00:17:05','2018-01-01 00:19:05', '2018-01-01 00:21:07', '2018-01-01 00:22:09', 
         '2018-01-01 00:22:17', '2018-01-01 00:25:09', '2018-01-01 00:25:11', '2018-01-01 00:27:28', '2018-01-01 00:29:29',
          '2018-01-01 00:30:35',  '2018-02-01 00:31:16', '2018-02-01 00:35:22', '2018-02-01 00:38:16', 
         '2018-02-01 00:38:20', '2018-02-01 00:55:15', '2018-01-03 00:55:22', 
         '2018-01-03 00:58:16', '2018-01-03 00:58:21', '2018-03-01 01:00:35', '2018-03-01 01:20:16', '2018-03-01 01:31:16'], 
'id': [np.nan, np.nan, 'a', 'a', 'b', np.nan, 'b', 'c', 'c', 'c', 'c', 'd', 'd', 'e', 'e', np.nan, 'f', 'f', 
       'g', np.nan, 'f', 'g']}

#create dataframe
df = pd.DataFrame(data=d)
#change date to datetime
df['date'] =  pd.to_datetime(df['date']) 
df

   player      date         id
0   1   2018-01-01 00:17:01 NaN
1   1   2018-01-01 00:17:05 NaN
2   1   2018-01-01 00:19:05 a
3   1   2018-01-01 00:21:07 a
4   1   2018-01-01 00:22:09 b
5   1   2018-01-01 00:22:07 NaN
6   1   2018-01-01 00:25:09 b
7   1   2018-01-01 00:25:11 c
8   1   2018-01-01 00:27:28 c
9   1   2018-01-01 00:29:29 c
10  1   2018-01-01 00:30:35 c
11  2   2018-02-01 00:31:16 d
12  2   2018-02-01 00:35:22 d
13  2   2018-02-01 00:38:16 e
14  2   2018-02-01 00:38:20 e
15  2   2018-02-01 00:55:15 NaN
16  3   2018-01-03 00:55:22 f
17  3   2018-01-03 00:58:16 f
18  3   2018-01-03 00:58:21 g
19  3   2018-03-01 01:00:35 NaN
20  3   2018-03-01 01:20:16 f
21  3   2018-03-01 01:31:16 g

Итак, это мои три столбца:

  1. 'player' - dtype = object
  2. 'сессия' (объект). Каждый идентификатор сеанса группирует набор действий (то есть строк в наборе данных), которые игроки реализовали в сети.
  3. 'date' (объект datetime) сообщает нам время, когда было выполнено каждое действие Проблема в этом наборе данных заключается в том, что у меня есть временные метки для каждого действия, но в некоторых из них отсутствует идентификатор сеанса. Я хочу сделать следующее: для каждого игрока я хочу дать метку идентификатора для пропущенных значений, основываясь на временной шкале. Действия, не имеющие идентификатора, могут быть помечены, если они попадают во временной диапазон (первое действие - последнее действие) определенного сеанса.

Хорошо, вот мои пропущенные значения:

df.loc[df.id.isnull(),'date']
0     2018-01-01 00:17:01
1     2018-01-01 00:17:05
5     2018-01-01 00:22:07
15    2018-02-01 00:55:15
19    2018-03-01 01:00:35

Обратите внимание, что у меня есть код плеера для каждого из них: мне не хватает только сессионного кода. Итак, я хочу сравнить временную метку каждого пропущенного значения с временной меткой сеанса соответствующих игроков. Я думал о том, чтобы вычислить с помощью группы первое и последнее действие для каждого сеанса, для каждого игрока (но я не знаю, является ли это наилучшим подходом).

my_agg = df.groupby(['player', 'id']).date.agg([min, max])
my_agg
                  min                      max
player  id      
1       a   2018-01-01 00:19:05   2018-01-01 00:21:07
        b   2018-01-01 00:22:09   2018-01-01 00:25:09
        c   2018-01-01 00:25:11   2018-01-01 00:30:35
2       d   2018-02-01 00:31:16   2018-02-01 00:35:22
        e   2018-02-01 00:38:16   2018-02-01 00:38:20
3       f   2018-01-03 00:55:22   2018-03-01 01:20:16
        g   2018-01-03 00:58:21   2018-03-01 01:31:16

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

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

  1. действие произошло между первой и последней датой определенного сеанса. В этом случае я хотел бы заполнить отсутствующее значение идентификатором этого сеанса, поскольку он явно принадлежит этому сеансу. Поэтому строка 5 набора данных должна быть помечена как «b», так как она находится в диапазоне b.
  2. Я бы обозначил как '0' сеанс, в котором действие произошло вне диапазона любого сеанса - например, первые два Nans и строка 15.
  3. Наконец, отметьте его как '-99', если невозможно связать действие с одним сеансом, поскольку оно произошло во временном диапазоне другого сеанса. Это случай строки 19, последней нан.

Желаемый выход : Подводя итог, результат должен выглядеть следующим образом:

  player      date         id
0   1   2018-01-01 00:17:01 0
1   1   2018-01-01 00:17:05 0
2   1   2018-01-01 00:19:05 a
3   1   2018-01-01 00:21:07 a
4   1   2018-01-01 00:22:09 b
5   1   2018-01-01 00:22:07 b
6   1   2018-01-01 00:25:09 b
7   1   2018-01-01 00:25:11 c
8   1   2018-01-01 00:27:28 c
9   1   2018-01-01 00:29:29 c
10  1   2018-01-01 00:30:35 c
11  2   2018-02-01 00:31:16 d
12  2   2018-02-01 00:35:22 d
13  2   2018-02-01 00:38:16 e
14  2   2018-02-01 00:38:20 e
15  2   2018-02-01 00:55:15 0
16  3   2018-01-03 00:55:22 f
17  3   2018-01-03 00:58:16 f
18  3   2018-01-03 00:58:21 g
19  3   2018-03-01 01:00:35 -99
20  3   2018-03-01 01:20:16 f
21  3   2018-03-01 01:31:16 g

Ответы [ 2 ]

0 голосов
/ 03 сентября 2018

В моем решении мне просто пришлось немного поработать, чтобы правильно применить функцию, написанную @ysearka в предыдущем вопросе stackoverflow - см. Здесь . Основная задача заключалась в том, чтобы применить его функцию player за игроком.

#define a function to sort the missing values (ysearka function from stackoverflow)
def my_custom_function(time):
    #compare every date event with the range of the sessions. 
    current_sessions = my_agg.loc[(my_agg['min']<time) & (my_agg['max']>time)]
    #store length, that is the number of matches. 
    count = len(current_sessions)
    #How many matches are there for any missing id value?
    # if 0 it means that no matches are found: the event lies outside all the possible ranges
    if count == 0:
        return 0
    #if more than one, it is impossible to say to which session the event belongs
    if count > 1:
        return -99
    #equivalent to if count == 1 return: in this case the event belongs clearly to just one session
    return current_sessions.index[0][1]


#create a list storing all the player ids
plist = list(df.player.unique())

#ignore settingcopywarning: https://stackoverflow.com/questions/20625582/how-to-deal-with-settingwithcopywarning-in-pandas
pd.options.mode.chained_assignment = None

# create an empty new dataframe, where to store the results
final = pd.DataFrame()
#with this for loop iterate over the part of the dataset corresponding to one player at a time
for i in plist:
    #slice the dataset by player
    players = df.loc[df['player'] == i]
    #for every player, take the dates where we are missing the id
    mv_per_player = players.loc[players.id.isnull(),'date']
    #for each player, groupby player id, and compute the first and last event
    my_agg = players.groupby(['player', 'id']).date.agg([min, max])
    #apply the function to each chunk of the dataset. You obtain a series, with all the imputed values for the Nan
    ema = mv_per_player.apply(my_custom_function)    
    #now we can sobstitute the missing id with the new imputed values...
    players.loc[players.id.isnull(),'id'] = ema.values    
    #append new values stored in players to the new dataframe
    final = final.append(players)

#...and check the new dataset
final

player  date    id
0   1   2018-01-01 00:17:01 0
1   1   2018-01-01 00:17:05 0
2   1   2018-01-01 00:19:05 a
3   1   2018-01-01 00:21:07 a
4   1   2018-01-01 00:22:09 b
5   1   2018-01-01 00:22:17 b
6   1   2018-01-01 00:25:09 b
7   1   2018-01-01 00:25:11 c
8   1   2018-01-01 00:27:28 c
9   1   2018-01-01 00:29:29 c
10  1   2018-01-01 00:30:35 c
11  2   2018-02-01 00:31:16 d
12  2   2018-02-01 00:35:22 d
13  2   2018-02-01 00:38:16 e
14  2   2018-02-01 00:38:20 e
15  2   2018-02-01 00:55:15 0
16  3   2018-01-03 00:55:22 f
17  3   2018-01-03 00:58:16 f
18  3   2018-01-03 00:58:21 g
19  3   2018-03-01 01:00:35 -99
20  3   2018-03-01 01:20:16 f
21  3   2018-03-01 01:31:16 g

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

0 голосов
/ 31 августа 2018

Возможно, это не лучший подход, но он работает. в основном я создаю несколько новых столбцов, используя shift, а затем использовал ваши условия, которые вы упомянули с помощью np.select:

 df['shift'] = df['id'].shift(1)
df['shift-1'] = df['id'].shift(-1)
df['merge'] = df[['shift','shift-1']].values.tolist()
df.drop(columns=['shift','shift-1'], inplace=True)

alpha = {np.nan:0,'a':1,'b':2,'c':3,'d':4,'e':5,'f':6,'g':7,'h':8}
diff = []
for i in range(len(df)):
    diff.append(alpha[df['merge'][i][1]] - alpha[df['merge'][i][0]])

df['diff'] = diff

conditions = [(df['id'].shift(1).eq(df['id'].shift(-1)) & (df['id'].isna()) & (df['player'].shift(1).eq(df['player'].shift(-1)))),

              (~df['id'].shift(1).eq(df['id'].shift(-1)) & (df['id'].isna()) & (df['player'].shift(1).eq(df['player']) | 
                                                                                df['player'].shift(-1).eq(df['player'])) &
              (~df['diff'] < 0)),

              (~df['id'].shift(1).eq(df['id'].shift(-1)) & (df['id'].isna()) & (df['player'].shift(1).eq(df['player']) | 
                                                                                df['player'].shift(-1).eq(df['player'])) &
              (df['diff'] < 0)),


             ]
choices = [df['id'].ffill(),
           0,
           -99
          ]
df['id'] = np.select(conditions, choices, default = df['id'])
df.drop(columns=['merge','diff'], inplace=True)
df

из:

    player  date              id
0   1   2018-01-01 00:17:01   0
1   1   2018-01-01 00:17:05   0
2   1   2018-01-01 00:19:05   a
3   1   2018-01-01 00:21:07   a
4   1   2018-01-01 00:22:09   b
5   1   2018-01-01 00:22:07   b
6   1   2018-01-01 00:25:09   b
7   1   2018-01-01 00:25:11   c
8   1   2018-01-01 00:27:28   c
9   1   2018-01-01 00:29:29   c
10  1   2018-01-01 00:30:35   c
11  2   2018-02-01 00:31:16   d
12  2   2018-02-01 00:35:22   d
13  2   2018-02-01 00:38:16   e
14  2   2018-02-01 00:38:20   e
15  2   2018-02-01 00:55:15   0
16  3   2018-01-03 00:55:22   f
17  3   2018-01-03 00:58:16   f
18  3   2018-01-03 00:58:21   g
19  3   2018-03-01 01:00:35  -99
20  3   2018-03-01 01:20:16   f
21  3   2018-03-01 01:31:16   g
...