Pandas применить функцию только к похожим группам - PullRequest
0 голосов
/ 07 мая 2020

Мне очень жаль, что я задаю много pandas вопросов, я пытаюсь найти расстояние между точками, но там, где существует только один экземпляр user_id, я не хочу передавать эти пары в функцию гаверсинуса , потому что нет второй пары для вычисления c расстояния. Пример моего кода с использованием случайных данных.

import numpy as np
import pandas as pd
from itertools import chain, repeat

#dict of random data
d = {
    'user_id':[4,4,1,2,2,2,7,9,9,10],
    'lat':10 + np.random.sample(10) * 80,
    'lon':10 + np.random.sample(10) * 80,
    'distance_km':list(chain.from_iterable(repeat(0.0,10) for y in range(1))),
    'speed_kms':list(chain.from_iterable(repeat(0.0,10) for y in range(1))),
    'time':['04:56:14','20:04:45','09:37:51','10:53:44','11:12:17','16:55:52',
            '04:49:31','16:23:53','03:23:21','14:21:04'],
    'char_class':[True,True,True,False,False,False,False,False,False,False],
    'type':list(chain.from_iterable(repeat('n/a',10) for z in range(1)))
}

#make df from dict
df = pd.DataFrame.from_dict(d,orient='index').transpose()
#sort the df
df.sort_values(['user_id','time','char_class'],ascending=[False,True,False],inplace=True)

#zip latlon together to make tuples for below
df['paired'] = list(zip(df.lat,df.lon))

#find elapsed time for user pairs. [not displayed to save some space]
df['elapsed'] = df.sort_values(['user_id','time']).groupby('user_id')['time']
                        .diff().fillna(pd.Timedelta(seconds=0)) 
#find dist from points
df.distance_km = df.groupby(['user_id','distance_km']).apply(
                     haversine_func(df['paired']-df['paired']
                     .shift())).fillna(0.0)

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

  user_id      lat      lon distance_km speed_kms      time char_class type                                   paired
9      10  18.3099  37.5837           0         0  14:21:04      False  n/a  (18.309894682687393, 37.58366365422602)
8       9  56.4528  61.2092           0         0  03:23:21      False  n/a   (56.45283963809377, 61.20921073451559)
7       9  33.6459  86.8489           0         0  16:23:53      False  n/a  (33.645852426505236, 86.84885897513892)
6       7  46.2825  72.0214           0         0  04:49:31      False  n/a   (46.28253581704867, 72.02142769672362)
0       4  39.9998  75.7089           0         0  04:56:14       True  n/a   (39.99977593402277, 75.70892442647698)
1       4  18.8519    44.46           0         0  20:04:45       True  n/a   (18.851925780806642, 44.4600341643822)
3       2  31.9623  74.1928           0         0  10:53:44      False  n/a  (31.962265405454502, 74.19275419456591)
4       2  71.3837  25.2423           0         0  11:12:17      False  n/a  (71.38367453710737, 25.242261227675755)
5       2  36.0831  43.7794           0         0  16:55:52      False  n/a   (36.08311981908213, 43.77939369677742)
2       1  61.1941  68.2175           0         0  09:37:51       True  n/a    (61.19413548303556, 68.2174579805573)

Я не хочу публиковать haversine_func, но tl; dr берет пары кортежей (и дополнительные единицы измерения) и вычисляет их, чтобы найти расстояние. Когда я передаю его в .apply, я получаю сообщение об ошибке:

File: 'blah', line 65:
lat1, lon1 = point1
ValueError: not enough values to unpack (expected 2 got 1)

Я предполагаю, что проблема связана с одиночным user_id s (1, 7, 10), имеющим только единственная точка. Есть ли способ, которым я могу pandas распознавать ТОЛЬКО дубликат user_id s и заполнять отдельные вхождения с помощью 0.0?

небольшого псевдокода для того, что я пытаюсь сделать:

for pairs in paired:
    if user_id == user_id.shift():
        haversine(paired, paired.shift())
        #replace df['distance'] with above haversine return vals, or 0.0 as appropriate

Я бы хотел, чтобы результат выглядел так:

  user_id      lat      lon distance_km speed_kms      time char_class type 
9      10  18.3099  37.5837         0.0         0  14:21:04      False  n/a
8       9  56.4528  61.2092         0.0         0  03:23:21      False  n/a
7       9  33.6459  86.8489   [calced_val]      0  16:23:53      False  n/a
6       7  46.2825  72.0214         0.0         0  04:49:31      False  n/a
0       4  39.9998  75.7089         0.0         0  04:56:14       True  n/a
1       4  18.8519    44.46   [calced_val]      0  20:04:45       True  n/a
3       2  31.9623  74.1928         0.0         0  10:53:44      False  n/a
4       2  71.3837  25.2423   [calced_val]      0  11:12:17      False  n/a
5       2  36.0831  43.7794   [calced_val]      0  16:55:52      False  n/a 
2       1  61.1941  68.2175         0.0         0  09:37:51       True  n/a 

1 Ответ

0 голосов
/ 07 мая 2020

Итак, я понял это через неудобное количество поисков в Google, проб и ошибок и отказавшись от чистого pandas решения.

В итоге я сделал:

d = {k:v['paired'].tolist() for k,v in df.groupby('user_id')

for k,v in d.items():
    if len(v)>1:
        x=0
        n=x+1
        newlist=[]
        while x<len(v):
            dist=haversine_func(v[x],v[n])
            newlist.append(dist)
            x+=1
        d[k]=newlist
    else:
        d[k]=[0.0]

tempdf = pd.DataFrame.from_dict(d, orient='index')
tempdf = tempdf.stack().reset_index(level=1,drop=True).to_frame(name='distance')
df['distance_km'] = tempdf['distance'].to_numpy
del tempdf

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

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