Векторизовать значение процентиля столбца B столбца A (для групп)

Для каждой пары городов аэропортов src и dest я хочу вернуть процентиль столбца a с учетом значения столбца b.

Я могу сделать это вручную как таковое:

Пример df только с двумя парами src/dest (у меня есть тысячи в моем фактическом df):

dt  src dest    a   b
0   2016-01-01  YYZ SFO 548.12  279.28
1   2016-01-01  DFW PDX 111.35  -65.50
2   2016-02-01  YYZ SFO 64.84   342.35
3   2016-02-01  DFW PDX 63.81   61.64
4   2016-03-01  YYZ SFO 614.29  262.83

{'a': {0: 548.12,
  1: 111.34999999999999,
  2: 64.840000000000003,
  3: 63.810000000000002,
  4: 614.28999999999996,
  5: -207.49000000000001,
  6: 151.31999999999999,
  7: -56.43,
  8: 611.37,
  9: -296.62,
  10: 6417.5699999999997,
  11: -376.25999999999999,
  12: 465.12,
  13: -821.73000000000002,
  14: 1270.6700000000001,
  15: -1410.0899999999999,
  16: 1312.6600000000001,
  17: -326.25999999999999,
  18: 1683.3699999999999,
  19: -24.440000000000001,
  20: 583.60000000000002,
  21: -5.2400000000000002,
  22: 1122.74,
  23: 195.21000000000001,
  24: 97.040000000000006,
  25: 133.94},
 'b': {0: 279.27999999999997,
  1: -65.5,
  2: 342.35000000000002,
  3: 61.640000000000001,
  4: 262.82999999999998,
  5: 115.89,
  6: 268.63999999999999,
  7: 2.3500000000000001,
  8: 91.849999999999994,
  9: 62.119999999999997,
  10: 778.33000000000004,
  11: -142.78,
  12: 1675.53,
  13: -214.36000000000001,
  14: 983.80999999999995,
  15: -207.62,
  16: 632.13999999999999,
  17: -132.53,
  18: 422.36000000000001,
  19: 13.470000000000001,
  20: 642.73000000000002,
  21: -144.59999999999999,
  22: 213.15000000000001,
  23: -50.200000000000003,
  24: 338.27999999999997,
  25: -129.69},
 'dest': {0: 'SFO',
  1: 'PDX',
  2: 'SFO',
  3: 'PDX',
  4: 'SFO',
  5: 'PDX',
  6: 'SFO',
  7: 'PDX',
  8: 'SFO',
  9: 'PDX',
  10: 'SFO',
  11: 'PDX',
  12: 'SFO',
  13: 'PDX',
  14: 'SFO',
  15: 'PDX',
  16: 'SFO',
  17: 'PDX',
  18: 'SFO',
  19: 'PDX',
  20: 'SFO',
  21: 'PDX',
  22: 'SFO',
  23: 'PDX',
  24: 'SFO',
  25: 'PDX'},
 'dt': {0: Timestamp('2016-01-01 00:00:00'),
  1: Timestamp('2016-01-01 00:00:00'),
  2: Timestamp('2016-02-01 00:00:00'),
  3: Timestamp('2016-02-01 00:00:00'),
  4: Timestamp('2016-03-01 00:00:00'),
  5: Timestamp('2016-03-01 00:00:00'),
  6: Timestamp('2016-04-01 00:00:00'),
  7: Timestamp('2016-04-01 00:00:00'),
  8: Timestamp('2016-05-01 00:00:00'),
  9: Timestamp('2016-05-01 00:00:00'),
  10: Timestamp('2016-06-01 00:00:00'),
  11: Timestamp('2016-06-01 00:00:00'),
  12: Timestamp('2016-07-01 00:00:00'),
  13: Timestamp('2016-07-01 00:00:00'),
  14: Timestamp('2016-08-01 00:00:00'),
  15: Timestamp('2016-08-01 00:00:00'),
  16: Timestamp('2016-09-01 00:00:00'),
  17: Timestamp('2016-09-01 00:00:00'),
  18: Timestamp('2016-10-01 00:00:00'),
  19: Timestamp('2016-10-01 00:00:00'),
  20: Timestamp('2016-11-01 00:00:00'),
  21: Timestamp('2016-11-01 00:00:00'),
  22: Timestamp('2016-12-01 00:00:00'),
  23: Timestamp('2016-12-01 00:00:00'),
  24: Timestamp('2017-01-01 00:00:00'),
  25: Timestamp('2017-01-01 00:00:00')},
 'src': {0: 'YYZ',
  1: 'DFW',
  2: 'YYZ',
  3: 'DFW',
  4: 'YYZ',
  5: 'DFW',
  6: 'YYZ',
  7: 'DFW',
  8: 'YYZ',
  9: 'DFW',
  10: 'YYZ',
  11: 'DFW',
  12: 'YYZ',
  13: 'DFW',
  14: 'YYZ',
  15: 'DFW',
  16: 'YYZ',
  17: 'DFW',
  18: 'YYZ',
  19: 'DFW',
  20: 'YYZ',
  21: 'DFW',
  22: 'YYZ',
  23: 'DFW',
  24: 'YYZ',
  25: 'DFW'}}

Я хочу процентиль на группу пар src и dest. Таким образом, для каждой пары должно быть только 1 процентное значение. Я хочу выполнить только процентиль b, где date = 2017-01-01 для каждой пары src и dest по всему столбцу a для каждой пары. Есть смысл?

Я могу сделать это вручную, например, для конкретной пары i.e. src=YYZ and dest=SFT:

from scipy import stats
import datetime as dt
import pandas as pd

p0 = dt.datetime(2017,1,1)

# lets slice df for src=YYZ and dest = SFO
x = df[(df.src =='YYZ') &
(df.dest =='SFO') &
(df.dt ==p0)].b.values[0]

# given B, what percentile does it fall in for the entire column A for YYZ, SFO
stats.percentileofscore(df['a'],x)
61.53846153846154

В приведенном выше случае я сделал это вручную для пар YYZ и SFO. Тем не менее, у меня есть тысячи пар в моем df.

Как мне vectorize использовать это с помощью pandas features, а не прокручивать каждую пару?

Должен быть способ использования groupby и использовать apply над функцией?

Мой желаемый df должен выглядеть примерно так:

    src dest  percentile
0   YYZ SFO   61.54
1   DFW PDX   23.07
2   XXX YYY   blahblah1
3   AAA BBB   blahblah2
...

UPDATE:

Я реализовал следующее:

def b_percentile_a(df,x,y,b):
    z = df[(df['src'] == x ) & (df['dest'] == y)].a
    r = stats.percentileofscore(z,b)
    return r

b_vector_df = df[df.dt == p0]

b_vector_df['p0_a_percentile_b'] = \
    b_vector_df.apply(lambda x: b_percentile_a(df,x.src,x.dest,x.b), axis=1)

Для пар 100 требуется 5.16 секунд. У меня есть пары 55,000. Таким образом, это займет ~50 минут. Мне нужно запустить этот 36 раз, чтобы он взял several days времени выполнения.

Должен быть более быстрый подход?

Ответ 1

Получено невероятное экономия времени!

Вывод:
Размер a_list: 49998 Рандомизированные уникальные значения
percentile_1 (ваш данный df - scipy)
рассчитанный процентили 104 раза - 104 записи в 0: 00: 07.777022

percentile_9 (класс PercentileOfScore (rank_searchsorted_list) с использованием заданного df)
рассчитанный процентили 104 раза - 104 записи в 0: 00: 00.000609
_ dt src dest a b pct scipy _ 0: 2016-01-01 YYZ SFO 54812 279.28 74.81299251970079 74.8129925197 1: 2016-01-01 DFW PDX 111.35 -65.5 24.66698667946718 24.6669866795 2: 2016-02-01 YYZ SFO 64.84 342.35 76.4810592423697 76.4810592424 3: 2016-02-01 DFW PDX 63.81 61.64 63.84655386215449 63.8465538622 ... 24: 2017-01-01 YYZ SFO 97.04 338.28 76.3570542821712 76.3570542822 25: 2017-01-01 DFW PDX 133.94 -129.69 21.4668586743469 21.4668586743

Посмотрев на реализацию scipy.percentileofscore, я обнаружил, что весь list( a ) - копируются, вставляются, сортируются, обыскиваются - при каждом вызове percentileofscore.

Я реализовал свой собственный class PercentileOfScore

import numpy as np
class PercentileOfScore(object):

    def __init__(self, aList):
        self.a = np.array( aList )
        self.a.sort()
        self.n = float(len(self.a))
        self.pct = self.__rank_searchsorted_list
    # end def __init__

    def __rank_searchsorted_list(self, score_list):
        adx = np.searchsorted(self.a, score_list, side='right')
        pct = []
        for idx in adx:
            # Python 2.x needs explicit type casting float(int)
            pct.append( (float(idx) / self.n) * 100.0 )

        return pct
    # end def _rank_searchsorted_list
# end class PercentileOfScore

Я не думаю, что def percentile_7 будет соответствовать вашим потребностям. dt не рассматривается.

PctOS = None
def percentile_7(df_flat):
    global PctOS
    result = {}
    for k in df_flat.pair_dict.keys():
        # df_flat.pair_dict = { 'src.dst': [b,b,...bn] }
        result[k] = PctOS.pct( df_flat.pair_dict[k] )

    return result
# end def percentile_7

В вашем примере с образцом вы используете целое df.a. В этом примере его dt_flat.a_list, но я не уверен, что это то, что вы хотите?

from PercentileData import DF_flat
def main():
    # DF_flat.data = {'dt.src.dest':[a,b]}
    df_flat = DF_flat()

    # Instantiate Global PctOS
    global PctOS
    # df_flat.a_list = [a,a,...an]
    PctOS = PercentileOfScore(df_flat.a_list)

    result = percentile_7(df_flat)
    # result = dict{'src.dst':[pct,pct...pctn]}

Протестировано с помощью Python: 3.4.2 и 2.7.9 - numpy: 1.8.2

Ответ 2

Предполагая, что у вас есть список пар, скажем pairs = [[a,b], [c,d], ...] и df,

    r = stats.percentileofscore(z,b)
    return r

for pair in pairs:
    # get the corresponding rows for each pair
    bvalues = df.loc[(df['src']==pair[0])&(df['dest']==pair[1])][['a', 'b']]
    # apply the percentileofscore map
    b_vector_df['p0_a_percentile_b'] = bvalues.b.apply(lambda x: stats.percentileofscore(bvalues.a, x))

Я не совсем уверен, в чем цель. Я понимаю, что вы прочитали значение b для каждой пары src, dest и найдите соответствующее значение a и затем вычислите процентиль этого значения a. Дайте мне знать, если это поможет:)

EDIT: если вы работаете только с пятью столбцами date, src, dest, a, and b, вы можете рассмотреть возможность работы с копией фрейма данных, который содержит только эти 5 столбцов. Это уменьшает объем работы, необходимый для каждого этапа извлечения. Я чувствую, что эффективнее работать только с объемом данных, который вам нужен. Выбор строк из Dataframe на основе значений в нескольких столбцах в pandas - это обсуждение, которое может быть релевантным для вас.

Ответ 3

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

# takes the b value at a specified point
# and returns its percentile of the full a array
def b_pct(df, p0):
    bval = df.b[df.dt==p0]
    assert bval.size == 1, 'can have only one entry per timestamp'
    bval = bval.values[0]
    # compute the percentile
    return (df.a < bval).sum() / len(df.a)

# splits the full dataframe up into groups by (src, dest) trajectory and
# returns a dataframe of the form src, dest, percentile
def trajectory_b_percentile(df, p0):
    percentile_df = pd.DataFrame([pd.Series([s, d, b_pct(g, p0)],
                                            index=['src', 'dest', 'percentile'])
                                  for ((s, d), g) in df.groupby(('src', 'dest'))])
    return percentile_df

Для сравнения, ваш код выше выплевывает

           dt  src dest       a       b  p0_a_percentile_b
24 2017-01-01  YYZ  SFO   97.04  338.28          23.076923
25 2017-01-01  DFW  PDX  133.94 -129.69          46.153846

тогда как `trajectory_b_percentile 'возвращает

   src dest  percentile
0  DFW  PDX   46.1538
1  YYZ  SFO   23.0769

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

Ответ 4

Кажется, что еще одно значительное ускорение получается путем преобразования всего в массивы numpy и построения процентилей также в виде массива numpy:

# Get airport strings as indices
_, ir = np.unique(df['src'].values,  return_inverse=True)
_, ic = np.unique(df['dest'].values, return_inverse=True)

# Get a and b columns
a = df['a'].values
b = df['b'].values

# Compute percentile scores in a numpy array
prc = np.zeros(a.shape)
for i in range(0, a.shape[0]):
    prc[i] = stats.percentileofscore(a[np.logical_and(ir==ir[i], ic==ic[i])], b[i])

На кадре данных с 24000 элементами (см. ниже), запуск %%timeit дает

1 loop, best of 3: 2.17 s per loop

Однако исходная версия

df['p0_a_percentile_b'] = \
df.apply(lambda x: b_percentile_a(df,x.src,x.dest,x.b), axis=1)

дает

1 loop, best of 3: 1min 2s per loop

который намного медленнее. Я также проверил, что оба фрагмента производят один и тот же вывод, запустив np.all(prc == df.p0_a_percentile_b.values), получив True.

Приложение:

Я построил dataframe, чтобы проверить это, и здесь я разделяю процесс воспроизводимости. Я взял 2000 пар аэропортов, используя 100 уникальных имен аэропортов, затем создал 12 строк данных для каждой пары, а затем создал случайные столбцы a и b.

import pandas as pd
import numpy as np
import scipy.stats as stats
import numpy.matlib as mat

# Construct dataframe

T=12
N_airports = 100
N_entries = 2000
airports = np.arange(0, N_airports).astype('string')

src  = mat.repmat(airports[np.random.randint(N_airports, size=(N_entries, ))], 1, T)
dest = mat.repmat(airports[np.random.randint(N_airports, size=(N_entries, ))], 1, T)
a    = np.random.uniform(size=N_entries*T)
b    = np.random.uniform(size=N_entries*T)

df = pd.DataFrame(np.vstack((src, dest, a, b)).T, columns=['src', 'dest', 'a', 'b'])

Ответ 5

Пожалуйста, проверьте и прокомментируйте, если это представляет вашу модель данных!

  • 6 ^ 6 Пары [AAA-ZZZ] = 46 656. Обычно каждая PAIR имеет 12 RECORDS
  • Это RECORD (0) PAIR (DFW PDX)
       dt        src dest       a           b
    0: 2016-01-01 DFW PDX   111.35       -65.5
    
  • Это SET (DFW PDX) = 13 RECORDS PAIR (DFW PDX)
       dt        src dest       a           b
    0: 2016-01-01 DFW PDX   111.35       -65.5
    1: 2016-02-01 DFW PDX    63.81       61.64
    2: 2016-03-01 DFW PDX  -207.49      115.89
    3: 2016-04-01 DFW PDX   -56.43        2.35
    4: 2016-05-01 DFW PDX  -296.62       62.12
    5: 2016-06-01 DFW PDX  -376.26     -142.78
    6: 2016-07-01 DFW PDX  -821.73     -214.36
    7: 2016-08-01 DFW PDX -1410.09     -207.62
    8: 2016-09-01 DFW PDX  -326.26     -132.53
    9: 2016-10-01 DFW PDX   -24.44       13.47
    10:2016-11-01 DFW PDX    -5.24      -144.6
    11:2016-12-01 DFW PDX   195.21       -50.2
    12:2017-01-01 DFW PDX   133.94     -129.69
    
  • Пример: подсчитать процентиль RECORD (0)
       dt        src dest       a           b
    0: 2016-01-01 DFW PDX   111.35       -65.5
    

    псевдокод:   stats.percentileofscore(SET (DFW PDX) [a0... a12], -65.5) = 46.15

  • Пример: вычислять процентиль SET (DFW PDX)

    ПСЕВДОКОД
        для записи в SET (DFW PDX):
          stats.percentileofscore(SET (DFW PDX) [a0... a12], record.b)
       Выход: pct0... pct12

    Использование rank_searchsorted_list не требует "для записи":
        rank_searchsorted_list (SET (DFW PDX) [a0... a12], SET (DFW PDX) [b0... b12])
       Выход: [pct0... pct12]

  • Это SET (DFW PDX), векторизованный

    OBJECT = {'DFW PDX':[
    ['2016-01-01', '2016-02-01', '2016-03-01', '2016-04-01', '2016-05-01', '2016-06-01', '2016-07-01', '2016-08-01', '2016-09-01', '2016-10-01', '2016-11-01', '2016-12-01', '2017-01-01']
    [111.35, 63.81, -207.49, -56.43, -296.62, -376.26, -821.73, -1410.09, -326.26, -24.44, -5.24, 195.21, 133.94]
    [-65.5, 61.64, 115.89, 2.35, 62.12, -142.78, -214.36, -207.62, -132.53, 13.47, -144.6, -50.2, -129.69]
    [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
    ]}  
    
  • Пример: вычислять проценты OBJECT (DFW PDX)
    Использование stats.percentileofscore:

    a = 1; b = 2
    for b_value in OBJECT['DFW PDX'][b]:
        stats.percentileofscore( OBJECT['DFW PDX'][a], b_value)
    Output: pct0...pct12  
    

    Использование rank_searchsorted_list не требует "для b_value in":

    a = 1; b = 2; pct = 3
    vector = OBJECT['DFW PDX']
    vector[pct] = rank_searchsorted_list( vector[a], vector[b] )  
    

    Вывод:

       dt         src dest      a           b       pct    scipy
    0: 2016-01-01 DFW PDX   111.35       -65.5     46.15   46.15
    1: 2016-02-01 DFW PDX    63.81       61.64     69.23   69.23
    2: 2016-03-01 DFW PDX  -207.49      115.89     84.61   84.61
    3: 2016-04-01 DFW PDX   -56.43        2.35     69.23   69.23
    4: 2016-05-01 DFW PDX  -296.62       62.12     69.23   69.23
    5: 2016-06-01 DFW PDX  -376.26     -142.78     46.15   46.15
    6: 2016-07-01 DFW PDX  -821.73     -214.36     38.46   38.46
    7: 2016-08-01 DFW PDX -1410.09     -207.62     38.46   38.46
    8: 2016-09-01 DFW PDX  -326.26     -132.53     46.15   46.15
    9: 2016-10-01 DFW PDX   -24.44       13.47     69.23   69.23
    10:2016-11-01 DFW PDX    -5.24      -144.6     46.15   46.15
    11:2016-12-01 DFW PDX   195.21       -50.2     53.84   53.84
    12:2017-01-01 DFW PDX   133.94     -129.69     46.15   46.15
    

Пожалуйста, проверьте и подтвердите расчетный процентиль!