Выберите сечение с несколькими ключами из DataFrame

У меня есть DataFrame "df" с столбцами данных (время, тиккер) Multiindex и bid/ask/etc:

                          tod    last     bid      ask      volume
    time        ticker                  
    2013-02-01  SPY       1600   149.70   150.14   150.17   1300
                SLV       1600   30.44    30.38    30.43    3892
                GLD       1600   161.20   161.19   161.21   3860

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

    df.xs('SPY', level=1)

который дает мне временные ряды SPY. Каков наилучший способ выбора поперечного сечения с несколькими ключами, то есть комбинированное поперечное сечение как SPY, так и GLD, что-то вроде:

    df.xs(['SPY', 'GLD'], level=1)

?

Ответы

Ответ 1

Преобразуйте в панель, затем индексирование будет прямым

In [20]: df = pd.DataFrame(dict(time = pd.Timestamp('20130102'), 
                                A = np.random.rand(3), 
                 ticker=['SPY','SLV','GLD'])).set_index(['time','ticker'])

In [21]: df
Out[21]: 
                          A
time       ticker          
2013-01-02 SPY     0.347209
           SLV     0.034832
           GLD     0.280951

In [22]: p = df.to_panel()

In [23]: p
Out[23]: 
<class 'pandas.core.panel.Panel'>
Dimensions: 1 (items) x 1 (major_axis) x 3 (minor_axis)
Items axis: A to A
Major_axis axis: 2013-01-02 00:00:00 to 2013-01-02 00:00:00
Minor_axis axis: GLD to SPY

In [24]: p.ix[:,:,['SPY','GLD']]
Out[24]: 
<class 'pandas.core.panel.Panel'>
Dimensions: 1 (items) x 1 (major_axis) x 2 (minor_axis)
Items axis: A to A
Major_axis axis: 2013-01-02 00:00:00 to 2013-01-02 00:00:00
Minor_axis axis: SPY to GLD

Ответ 2

Я не мог найти более прямой способ, кроме использования select:

>>> df

       last   tod
A SPY     1  1600
  SLV     2  1600
  GLD     3  1600

>>> df.select(lambda x: x[1] in ['SPY','GLD'])

       last   tod
A SPY     1  1600
  GLD     3  1600

Ответ 3

Есть лучшие способы сделать это с более поздними версиями Pandas:

regression_df.loc[(slice(None), ['SPY', 'GLD']), :]

Этот подход требует, чтобы индекс был лексикографически отсортирован (используйте df.sort_index()).

Ответ 4

Для чего это необходимо, я сделал следующее:

foo = pd.DataFrame(np.random.rand(12,3), 
                   index=pd.MultiIndex.from_product([['A','B','C','D'],['Green','Red','Blue']], 
                                                    names=['Letter','Color']),
                   columns=['X','Y','Z']).sort_index()

foo.reset_index()\
   .loc[foo.reset_index().Color.isin({'Green','Red'})]\
   .set_index(foo.index.names)

Этот подход похож на select, но избегает итерации по всем строкам с помощью лямбда.

Однако я сравнивал это с подходом Panel, и кажется, что решение Panel быстрее (2,91 мс для индекса /loc vs 1,48 мс для to_panel/to_frame:

foo.to_panel()[:,:,['Green','Red']].to_frame()

Время:

In [56]:
%%timeit
foo.reset_index().loc[foo.reset_index().Color.isin({'Green','Red'})].set_index(foo.index.names)
100 loops, best of 3: 2.91 ms per loop

In [57]:
%%timeit
foo2 = foo.reset_index()
foo2.loc[foo2.Color.eq('Green') | foo2.Color.eq('Red')].set_index(foo.index.names)
100 loops, best of 3: 2.85 ms per loop

In [58]:
%%timeit
foo2 = foo.reset_index()
foo2.loc[foo2.Color.ne('Blue')].set_index(foo.index.names)
100 loops, best of 3: 2.37 ms per loop

In [54]:
%%timeit
foo.to_panel()[:,:,['Green','Red']].to_frame()
1000 loops, best of 3: 1.18 ms per loop

UPDATE

После повторного просмотра этой темы (снова) я заметил следующее:

In [100]:
%%timeit
foo2 = pd.DataFrame({k: foo.loc[k] for k in foo.index if k[1] in ['Green','Red']}).transpose()
foo2.index.names = foo.index.names
foo2.columns.names = foo2.columns.names
100 loops, best of 3: 1.97 ms per loop

In [101]:
%%timeit
foo2 = pd.DataFrame.from_dict({k: foo.loc[k] for k in foo.index if k[1] in ['Green','Red']}, orient='index')
foo2.index.names = foo.index.names
foo2.columns.names = foo2.columns.names
100 loops, best of 3: 1.82 ms per loop

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

%%timeit
pd.concat({key: foo.xs(key, axis=0, level=1) for key in ['Green','Red']}, axis=0)
1000 loops, best of 3: 1.31 ms per loop

И если вы просто выбираете на первом уровне:

%%timeit
pd.concat({key: foo.loc[key] for key in ['A','B']}, axis=0, names=foo.index.names)
1000 loops, best of 3: 1.12 ms per loop

против

%%timeit
foo.to_panel()[:,['A','B'],:].to_frame()
1000 loops, best of 3: 1.16 ms per loop

Другое обновление

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

%%timeit
foo.loc[(slice(None), ['Blue','Red']),:]
1000 loops, best of 3: 582 µs per loop

Это наиболее эффективный и интуитивно понятный, по моему мнению (пользователю не нужно понимать панели и как они созданы из фреймов).

Примечание. Даже если индекс еще не отсортирован, сортировка индекса foo "на лету" сопоставима по производительности с опцией to_panel.