2017-01-11 2 views
0

У меня есть большой фреймворк данных, содержащий журналы пользователей на веб-сайте, и мне нужно найти продолжительность каждого посещения для каждого пользователя.Уменьшить время выполнения кода pandas

У меня есть 3,5 миллиона строк и 450 тысяч одиноких пользователей.

это мой код:

temp=df["server.REMOTE_ADDR"]# main df with timestamps and ip adresses 
user_db = df["server.REMOTE_ADDR"]# df with all IP adresses 

user_db = user_db.drop_duplicates() # drop duplicate IP 
time_thresh = 15*60 # if user inactive for 15 minutes, it's a new visit 
temp_moyen=[] # array for mean times 
temp_min=[] # array for minimal time 
temp_max=[] # array for max time 
nb_visites=[] # array for number of visit 

for k,user in enumerate(user_db.values): # for each user 
    print("User {}/{}").format(k+1,len(user_db.values)) 
    t0=[] # time of beginning of visit 
    tf=[] # time of end of visit 
    times_db = df[temp == user]["server.date"].values # retrieve all timestamps for current user 
    times_db = [dateutil.parser.parse(times) for times in times_db] # parse to datetime 
    i=1 
    last_t = times_db[0] 
    delta = 0 
    while i<len(times_db): # while there is still a timestamp in the list 
     t0.append(times_db[i-1]) # begin the first visit 
     delta=0 
     while (delta < time_thresh and i<len(times_db)): # while not inactive for 15 minutes 
      delta = (times_db[i]-last_t).total_seconds() 
      last_t = times_db[i] 
      i+=1 
     if i!=len(times_db): #if not last run 
      tf.append(times_db[i-2]) 
     else: # if no more timestamp, record the last one as end of last visit 
      tf.append(times_db[-1]) 
    if len(times_db)<=1: # if only one timestamp, tf = t0 
     tf.append(times_db[-1]) 

    diff=[(final-first).total_seconds() for first,final in zip(t0,tf)] # evaluate diff between each t0 and tf 
    temp_moyen.append(np.mean(diff)) # add to the lists 
    temp_min.append(np.min(diff)) 
    temp_max.append(np.max(diff)) 
    nb_visites.append(len(diff)) 

user_db=user_db.to_frame() # convert to dataframe 
user_db["temp_moyen"]=temp_moyen # add columns for each information (mean,min,max,number of visits) 
user_db["temp_min"]=temp_min 
user_db["temp_max"]=temp_max 
user_db["nb_visites"]=nb_visites 

Этот код работает, но очень медленно: 200 пользователей/мин на моем компьютере. Что можно посмотреть:

  • определить узкое полость?

  • ускорить его?

EDIT: В соответствии с просьбой, мои данные выглядят следующим образом: пар каждый пользователь, у меня есть список временных меток: [100, 101, 104, 106, 109, 200, 209, 211, 213]

Мне нужно найти, сколько посещений один пользователь сделал, например, в этот случай, это будет представлять два посещения, 100-109 и 200-213. Первый визит продолжился 9, второй - 13, поэтому я могу иметь среднее, мин и максимальное количество посещений.

EDIT 2: Узкие здесь (277ms из 300мса на петлю):

times_db = df[temp == user]["server.date"].values # retrieve all timestamps for current user 

Я положил его в списке понимания, прежде чем цикл, но он по-прежнему медленно:

times_db_all = [df[temp == user]["server.date"].values for user in user_db.values] 

%timeit times_db_all = [df_temp[temp == user]["server.date"].values for user in user_db.values[0:3]] 
1 loops, best of 3: 848 ms per loop #848ms for 3 users !! 

мой дб выглядит следующим образом:

user_ip | server.date 
1.1.1.1 datetime.datetime(2017, 1, 3, 0, 0, 3, tzinfo=tzutc()), 
1.1.1.1 datetime.datetime(2017, 1, 4, 1, 7, 30, tzinfo=tzutc()), 
3.3.3.3 datetime.datetime(2017, 1, 4, 5, 58, 52, tzinfo=tzutc()), 
1.1.1.1 datetime.datetime(2017, 1, 10, 16, 22, 56, tzinfo=tzutc()) 
4.4.4.4 datetime.datetime(2017, 1, 10, 16, 23, 01, tzinfo=tzutc()) 
.... 
+0

Первый вопрос, очевидно, необходимо ответить первым, для этого вы можете попробовать профилировать https://docs.python.org/2/library/profile.html. Это определит, какие процедуры занимают больше всего времени, как часто их вызывают и т. Д. И упреждающий ответ на второй вопрос заключается в том, что циклы обычно медленнее векторной операции. – KeithWM

+0

Я согласен с вашей точкой в ​​операции с вектором, я просто не вижу, как применить его в моем случае. Я попробую профилировать – CoMartel

+2

Лучше было бы предоставить примерные данные, объясняющие, что вы пытаетесь сделать, и то, что вы ожидаете от результата. – piRSquared

ответ

2

Чтобы продолжить с моим комментарием об удалении тыс e loop: как я вижу, у вас есть куча временных меток активности, и вы предполагаете, что пока эти временные метки близки друг к другу, они относятся к одному посещению, и в противном случае они представляют разные посещения. Например, [100, 101, 104, 106, 109, 200, 209, 211, 213] будет представлять два посещения, 100-109 и 200-213. Чтобы ускорить этот процесс, вы можете сделать следующее с помощью scipy:

import scipy 

cutoff = 15 

times = scipy.array([100, 101, 104, 106, 109, 200, 209, 211, 213, 300, 310, 325]) 
delta = times[1:] - times[:-1] 
which = delta > cutoff # identifies which gaps represent a new visit 
N_visits = which.sum() + 1 # note the +1 for 'fence post' 
L_boundaries = scipy.zeros((N_visits,)) # generating these arrays might be unnecessary and relatvely slow 
R_boundaries = scipy.zeros((N_visits,)) 
L_boundaries[1:] = times[1:][which] 
R_boundaries[:-1] = times[:-1][which] 
visit_lengths = R_boundaries - L_boundaries 

Это, вероятно, может быть даже быстрее, но это, вероятно, уже намного быстрее, чем ваш текущий цикл.

Следующая, вероятно, немного быстрее, за счет ясности в коде

import scipy 

cutoff = 15 

times = scipy.array([100, 101, 104, 106, 109, 200, 209, 211, 213, 300, 310, 325]) 
which = times[1:] - times[:-1] > cutoff 
N_visits = which.sum() + 1 # fence post 
visit_lengths = scipy.zeros((N_visits,)) # it is probably inevitable to have to generate this new array 
visit_lengths[0] = times[:-1][which][0] - times[0] 
visit_lengths[1:-1] = times[:-1][which][1:] - times[1:][which][:-1] 
visit_lengths[-1] = times[-1] - times[1:][which][-1] 

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

EDIT на основе OP EDIT

Вы, возможно, должны смотреть на http://pandas.pydata.org/pandas-docs/stable/indexing.html. Я думаю, что медленный факт заключается в том, что вы делаете копию части вашего фрейма данных для каждого пользователя, то есть df[temp == user] создает новую фреймворк данных и сохраняет ее как times_db, возможно, было бы быстрее поместить полученные значения в массив numpy?Вы также можете выполнить синтаксический анализ для datetime сначала для всего фрейма данных.

+0

Вы правы, это должно быть быстрее. Я попробую, спасибо – CoMartel

+0

Я нашел узкое место и отредактировал мой пост – CoMartel

1

Я не могу видеть выборочные данные, так вот мой совет:

  • Прежде чем пытаться оптимизировать свой код, я предлагаю вам использовать profiler, чтобы получить статистику вашего кода.

    import cProfile
    cProfile.run('foo()')

    или python -m cProfile foo.py И вы можете получить статистику, которая описывает, как часто и как долго различные части программы executed.This является необходимым условием оптимизации.

  • Если ваши данные являются многомерными массивами и матрицами, попробуйте pandas или numpy, это ускорит ваш код.

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

  • Попробуйте устранить распространенные подвыражения, где в плотных петлях.

Надеюсь, это поможет.

Смежные вопросы