Panda DataFrame veya Serilere birden çok filtre uygulamak için etkili bir yol


148

Bir kullanıcı bir Pandas DataFrame veya Series nesnesine birkaç filtre uygulamak istediği bir senaryo var. Aslında, kullanıcı tarafından çalışma zamanında belirtilen bir grup filtreleme (karşılaştırma işlemleri) verimli bir şekilde zincirlemek istiyorum.

Filtreler katkı maddesi olmalıdır (yani uygulanan her biri sonuçları daraltmalıdır).

Şu anda kullanıyorum reindex()ama bu her seferinde yeni bir nesne oluşturur ve temel verileri kopyalar (belgeleri doğru anlıyorsam). Bu nedenle, büyük bir Seri veya DataFrame'i filtrelerken bu gerçekten verimsiz olabilir.

Ben kullanarak o düşünüyorum apply(), map()ya da benzer bir şey daha iyi olabilir. Yine de her şey etrafında kafamı sarmaya çalışırken Pandalar için oldukça yeniyim.

TL; DR

Aşağıdaki formun sözlüğünü almak ve her işlemi belirli bir Series nesnesine uygulamak ve bir 'filtre' seri nesnesini döndürmek istiyorum.

relops = {'>=': [1], '<=': [1]}

Uzun Örnek

Şu anda sahip olduğum bir örnekle başlayacağım ve sadece tek bir Series nesnesini filtreleyeceğim. Şu anda kullandığım işlev aşağıdadır:

   def apply_relops(series, relops):
        """
        Pass dictionary of relational operators to perform on given series object
        """
        for op, vals in relops.iteritems():
            op_func = ops[op]
            for val in vals:
                filtered = op_func(series, val)
                series = series.reindex(series[filtered])
        return series

Kullanıcı yapmak istediği işlemleri içeren bir sözlük sağlar:

>>> df = pandas.DataFrame({'col1': [0, 1, 2], 'col2': [10, 11, 12]})
>>> print df
>>> print df
   col1  col2
0     0    10
1     1    11
2     2    12

>>> from operator import le, ge
>>> ops ={'>=': ge, '<=': le}
>>> apply_relops(df['col1'], {'>=': [1]})
col1
1       1
2       2
Name: col1
>>> apply_relops(df['col1'], relops = {'>=': [1], '<=': [1]})
col1
1       1
Name: col1

Yine, yukarıdaki yaklaşımımdaki 'sorun', aradaki adımlar için verilerin çok fazla gereksiz kopyalanması olduğunu düşünüyorum.

Ayrıca, bu sözlüğü, sözlüğün, operatör için sütunları içerebilmesi ve giriş sözlüğüne dayalı olarak tüm bir DataFrame'i filtreleyebilmesi için genişletmek istiyorum. Ancak, ben dizi için ne işe yarıyor varsayalım kolayca bir DataFrame genişletilebilir.


Ayrıca, soruna bu yaklaşımın çok iyi olabileceğinin tamamen farkındayım. Bu yüzden belki de tüm yaklaşımı yeniden düşünmek faydalı olacaktır. Kullanıcıların çalışma zamanında bir dizi filtre işlemi belirtmesine ve yürütmelerine izin vermek istiyorum.
durden2.0

Pandaların data.table ile benzer şeyler yapıp yapamayacağını merak ediyorum: df [col1 <1 ,,] [col2> = 1]
xappppp

df.queryve pd.evalkullanım durumunuza uygun görünüyor. pd.eval()İşlevler ailesi, özellikleri ve kullanım örnekleri hakkında bilgi için , lütfen pd.eval () kullanarak pandalarda Dinamik İfade Değerlendirme sayfasını ziyaret edin .
cs95

Yanıtlar:


245

Pandalar (ve numpy) , çok daha verimli olacak olan boole endekslemesine izin verir :

In [11]: df.loc[df['col1'] >= 1, 'col1']
Out[11]: 
1    1
2    2
Name: col1

In [12]: df[df['col1'] >= 1]
Out[12]: 
   col1  col2
1     1    11
2     2    12

In [13]: df[(df['col1'] >= 1) & (df['col1'] <=1 )]
Out[13]: 
   col1  col2
1     1    11

Bunun için yardımcı işlevler yazmak istiyorsanız, bu satırlar boyunca bir şey düşünün:

In [14]: def b(x, col, op, n): 
             return op(x[col],n)

In [15]: def f(x, *b):
             return x[(np.logical_and(*b))]

In [16]: b1 = b(df, 'col1', ge, 1)

In [17]: b2 = b(df, 'col1', le, 1)

In [18]: f(df, b1, b2)
Out[18]: 
   col1  col2
1     1    11

Güncelleme: pandalar 0.13, bu tür kullanım durumları için bir sorgu yöntemine sahiptir , sütun adlarının aşağıdaki işlerin geçerli tanımlayıcıları olduğunu varsayarsak (ve sahnelerin arkasında numexpr kullandığından büyük çerçeveler için daha verimli olabilir ):

In [21]: df.query('col1 <= 1 & 1 <= col1')
Out[21]:
   col1  col2
1     1    11

1
Verilerinizin bir kopyasını oluşturmadığından, boolean hakkınız daha verimlidir. Ancak, benim senaryom örnek daha biraz daha zor. Aldığım girdi, hangi filtrelerin uygulanacağını tanımlayan bir sözlüktür. Örneğim böyle bir şey yapabilirdi df[(ge(df['col1'], 1) & le(df['col1'], 1)]. Benim için sorun gerçekten filtreleri ile sözlük çok sayıda operatör içerebilir ve onları zincirleme hantal. Belki büyük bir diziye her ara boolean dizi eklemek ve sonra sadece onlara operatör mapuygulamak için kullanabilirsiniz and?
durden2.0

@ durden2.0 Bir yardımcı fonksiyon için bir fikir ekledim, bu da aradığınıza benzer olduğunu düşünüyorum :)
Andy Hayden

Bu benim bulduğum şeye çok yakın görünüyor! Örnek için teşekkürler. Neden sadece yerine f()almak gerekiyor ? Bu, kullanıcının hala isteğe bağlı parametresini kullanabilmesi için mi? Bu başka bir küçük soruya yol açar. Diziden geçiş yapmaktan döndürülenin performans faydası / ticareti nedir? Tekrar teşekkürler! *bbf()outlogical_and()out()logical_and()
durden2.0

Boşver, yeterince yakından bakmadım. Bu *bgereklidir çünkü iki diziyi geçiyorsunuz b1ve b2arama yaparken bunları açmanız gerekiyor logical_and. Ancak, diğer soru hala devam etmektedir. Bir diziyi outparametre ile logical_and()sadece 'dönüş değerini kullanarak geçmek için bir performans avantajı var mı ?
durden2.0

2
@ dwanderson, birden çok koşul için np.logical_and.reduce öğesine bir koşul listesi iletebilirsiniz. Örnek: np.logical_and.reduce ([df ['a'] == 3, df ['b']> 10, df ['c']. İsin (1,3,5)])
Kuzenbo

39

Zincirleme koşulları, pep8 tarafından caydırılan uzun çizgiler oluşturur. .Query yöntemini kullanmak, güçlü ancak tek sesli ve çok dinamik olmayan dizeleri kullanmaya zorlar.

Filtrelerin her biri yerine oturduğunda, bir yaklaşım

import numpy as np
import functools
def conjunction(*conditions):
    return functools.reduce(np.logical_and, conditions)

c_1 = data.col1 == True
c_2 = data.col2 < 64
c_3 = data.col3 != 4

data_filtered = data[conjunction(c1,c2,c3)]

np.logical çalışır ve hızlıdır, ancak functools.reduce tarafından ele alınan ikiden fazla argüman almaz.

Bunun hala fazlalıklara sahip olduğunu unutmayın: a) küresel düzeyde kısayol oluşmaz b) Her bir koşul, başlangıç ​​verilerinin tamamında çalışır. Yine de, bunun birçok uygulama için yeterince verimli olmasını bekliyorum ve çok okunabilir.

Bunun yerine aşağıdakileri kullanarak da (burada koşullardan yalnızca birinin doğru olması gerekir) bir ayrılma yapabilirsiniz np.logical_or:

import numpy as np
import functools
def disjunction(*conditions):
    return functools.reduce(np.logical_or, conditions)

c_1 = data.col1 == True
c_2 = data.col2 < 64
c_3 = data.col3 != 4

data_filtered = data[disjunction(c1,c2,c3)]

1
Bunu değişken sayıda koşul için uygulamanın bir yolu var mı? Her ekleme denedi c_1, c_2, c_3, ... c_nbir listede, ardından geçen data[conjunction(conditions_list)]ancak bir hata olsun ValueError: Item wrong length 5 instead of 37.Ayrıca çalıştı data[conjunction(*conditions_list)]ama ben daha farklı bir sonuç almak data[conjunction(c_1, c_2, c_3, ... c_n )]emin oluyor gibi değil.
user5359531

Hata başka bir yerde bir çözüm buldu. data[conjunction(*conditions_list)]veri çerçevelerini bir listeye paketledikten ve listeyi yerinde açtıktan sonra çalışır
user5359531

1
Daha önce çok daha eğimli bir versiyonla yukarıdaki cevaba bir yorum bıraktım ve cevabınızı fark ettim. Çok temiz, çok beğendim!
dwanderson

Bu harika bir cevap!
Charlie Crown

1
Ben kullanmıştım: vb df[f_2 & f_3 & f_4 & f_5 ]ile f_2 = df["a"] >= 0. Bu işleve gerek yok ... (yüksek dereceli fonksiyon güzel olsa da ...)
A. Rabus

19

En Basit Çözümler:

kullanın:

filtered_df = df[(df['col1'] >= 1) & (df['col1'] <= 5)]

Başka bir örnek , Şubat 2018'e ait değerlere ilişkin veri çerçevesini filtrelemek için aşağıdaki kodu kullanın

filtered_df = df[(df['year'] == 2018) & (df['month'] == 2)]

sabit yerine değişken kullanıyorum. hata alıyorum. df [df []] [df []] uyarı mesajı verir, ancak doğru cevap verir.
Nguai al

8

Yana pandalar 0.22 güncellemesi , karşılaştırma seçenekleri gibidir:

  • gt (büyüktür)
  • lt (küçüktür)
  • eq (eşittir)
  • ne (eşit değil)
  • ge (büyük veya eşittir)

ve daha fazlası. Bu işlevler boole dizisini döndürür. Onları nasıl kullanabileceğimizi görelim:

# sample data
df = pd.DataFrame({'col1': [0, 1, 2,3,4,5], 'col2': [10, 11, 12,13,14,15]})

# get values from col1 greater than or equals to 1
df.loc[df['col1'].ge(1),'col1']

1    1
2    2
3    3
4    4
5    5

# where co11 values is better 0 and 2
df.loc[df['col1'].between(0,2)]

 col1 col2
0   0   10
1   1   11
2   2   12

# where col1 > 1
df.loc[df['col1'].gt(1)]

 col1 col2
2   2   12
3   3   13
4   4   14
5   5   15

2

Bunu neden yapmıyorsunuz?

def filt_spec(df, col, val, op):
    import operator
    ops = {'eq': operator.eq, 'neq': operator.ne, 'gt': operator.gt, 'ge': operator.ge, 'lt': operator.lt, 'le': operator.le}
    return df[ops[op](df[col], val)]
pandas.DataFrame.filt_spec = filt_spec

Demo:

df = pd.DataFrame({'a': [1,2,3,4,5], 'b':[5,4,3,2,1]})
df.filt_spec('a', 2, 'ge')

Sonuç:

   a  b
 1  2  4
 2  3  3
 3  4  2
 4  5  1

'A' sütununun> = 2 olduğu yerde filtrelendiğini görebilirsiniz.

Bu, operatör zincirlemesinden biraz daha hızlıdır (yazma zamanı, performans değil). Tabii ki içe aktarmayı dosyanın üstüne koyabilirsiniz.


1

e ayrıca listede bulunmayan veya yinelenebilir olmayan bir sütunun değerlerine dayalı satırlar da seçebilir. Daha önce olduğu gibi boolean değişkeni yaratacağız, ama şimdi ön tarafa ~ yerleştirerek boolean değişkenini yok edeceğiz.

Örneğin

list = [1, 0]
df[df.col1.isin(list)]
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.