2017-07-20 57 views
7

Zasadniczo chcę nauczyć się szybszego sposobu na cięcie ramki danych Pandy za pomocą warunkowego podziału na podstawie wyrażenia regularnego. Na przykład następujące df (nie więcej niż 4 różnice w string_column są jedynie w celach poglądowych)Krojenie wierszy Pandy z ciągami dopasowanymi powoli

index, string_col1, string_col2, value 
0, 'apple', 'this', 10 
1, 'pen', 'is', 123 
2, 'pineapple', 'sparta', 20 
3, 'pen pineapple apple pen', 'this', 234 
4, 'apple', 'is', 212 
5, 'pen', 'sparta', 50 
6, 'pineapple', 'this', 69 
7, 'pen pineapple apple pen', 'is', 79 
8, 'apple pen', 'sparta again', 78 
... 
100000, 'pen pineapple apple pen', 'this is sparta', 392 

trzeba zrobić logiczną segmentację warunkowego według string_column za pomocą wyrażenia regularnego przy poszukiwaniu wskaźników z minimalnym i maksimum w kolumnie wartości, a następnie w końcu znalezienie różnicy między wartością minimalną i maksymalną. Robię to w następujący sposób, ale to SUPER SLOW kiedy trzeba dopasować różne wzory regex:

pat1 = re.compile('apple') 
pat2 = re.compile('sparta') 
mask = (df['string_col1'].str.contains(pat1)) & (df['string_col2'].str.contains(pat2)) 
max_idx = df[mask].idxmax() 
min_idx = df[mask].idxmin() 
difference = df['value'].loc[max_idx] - df['value'].loc[min_idx] 

myślę, aby uzyskać jeden „różnicy” odpowiedź, ja krojenie DF zbyt wiele razy, ale Nie mogę wymyślić, jak to zrobić mniej. Co więcej, czy istnieje szybszy sposób na podzielenie go?

To jest pytanie optymalizacyjne, ponieważ wiem, że mój kod zapewnia mi to, czego potrzebuję. Wszelkie wskazówki zostaną docenione!

+0

Można połączyć wyrażenie regularne w pojedyncze wyrażenie, a następnie maska ​​jest prawdopodobnie szybsza. patX = re.compile ('(apple | sprata)'). Czy to przyspiesza? Dodatkowo, tworzenie maski nad całą ramką DataFrame w celu uzyskania pierwszego indeksu może nie być najszybsze. –

+0

Muszę wykonać dwa oddzielne sprawdzenia regex dla dwóch różnych wzorów dla dwóch różnych kolumn, więc nie jestem pewien, czy łączenie ich w jedno wyrażenie regularne i dopasowanie w dwóch kolumnach jest dobrym pomysłem. –

+0

Wygląda bardzo podobnie do https://stackoverflow.com/questions/40183800/pandas-difference-between-lubgest-and-smallest-value-within-group –

Odpowiedz

1

przejść każdy maska ​​do kolejnej podgrupie dataframe, każda nowa filtracja dzieje w mniejszej podgrupie oryginalnego dataframe:

pat1 = re.compile('apple') 
pat2 = re.compile('sparta') 
mask1 = df['string_col1'].str.contains(pat1) 
mask = (df[mask1]['string_col2'].str.contains(pat2)) 
df1=df[mask1][mask] 
max_idx = df1['value'].idxmax() 
min_idx = df1['value'].idxmin() 
a,b=df1['value'].loc[max_idx],df1['value'].loc[min_idx] 
+0

Czy możesz wyjaśnić, dlaczego miałoby to być szybsze? –

+0

ponieważ każde nowe filtrowanie odbywa się na mniejszym podzbiorze oryginalnej ramki danych – denfromufa

+0

Jest to rozsądny pomysł, ale niewiele pomoże, jeśli wszystkie wzory pasują do – ead

1

Próbowałem do profilu Twojego przykładu, ale ja rzeczywiście coraz dość świetna wydajność na moich syntetycznych danych, więc może potrzebuję wyjaśnienia. (Również z jakiegoś powodu .idxmax() zrywa dla mnie, gdy mam ciąg w mojej ramce danych).

Oto mój kod badania:

import pandas as pd 
import re 
import numpy as np 
import random 
import IPython 
from timeit import default_timer as timer 

possibilities_col1 = ['apple', 'pen', 'pineapple', 'joseph', 'cauliflower'] 
possibilities_col2 = ['sparta', 'this', 'is', 'again'] 
entries = 100000 
potential_words_col1 = 4 
potential_words_col2 = 3 
def create_function_col1(): 
    result = [] 
    for x in range(random.randint(1, potential_words_col1)): 
     result.append(random.choice(possibilities_col1)) 
    return " ".join(result) 

def create_function_col2(): 
    result = [] 
    for x in range(random.randint(1, potential_words_col2)): 
     result.append(random.choice(possibilities_col2)) 
    return " ".join(result) 

data = {'string_col1': pd.Series([create_function_col1() for _ in range(entries)]), 
     'string_col2': pd.Series([create_function_col2() for _ in range(entries)]), 
     'value': pd.Series([random.randint(1, 500) for _ in range(entries)])} 


df = pd.DataFrame(data) 
pat1 = re.compile('apple') 
pat2 = re.compile('sparta') 
pat3 = re.compile('pineapple') 
pat4 = re.compile('this') 
#IPython.embed() 
start = timer() 
mask = df['string_col1'].str.contains(pat1) & \ 
     df['string_col1'].str.contains(pat3) & \ 
     df['string_col2'].str.contains(pat2) & \ 
     df['string_col2'].str.contains(pat4) 
valid = df[mask] 
max_idx = valid['value'].argmax() 
min_idx = valid['value'].argmin() 
#max_idx = result['max'] 
#min_idx = result['min'] 
difference = df.loc[max_idx, 'value'] - df.loc[min_idx, 'value'] 
end = timer() 
print("Difference: {}".format(difference)) 
print("# Valid: {}".format(len(valid))) 
print("Time Elapsed: {}".format(end-start)) 

Czy możesz wyjaśnić ile warunki starasz? (Każde wyrażenie, które dodaję dodaje tylko z grubsza liniowy przyrost czasu (tj. 2-> 3 wyrażenie regularne oznacza 1,5-krotny wzrost czasu wykonywania)). Dostaję również liniowe skalowanie liczby wpisów i obu potencjalnych długości łańcuchów (zmienne potencjalnego_words).

Dla odniesienia, ten kod jest oceniany w ~ 0,15 sekundy na moim komputerze (1 milion wpisów trwa ~ 1,5 sekundy).

Edytuj: Jestem idiotą i nie robiłem tego samego, co ty (brałem różnicę między wartościami przy najmniejszych i największych indeksach w zestawie danych, a nie różnicą między najmniejszą a największą wartością), ale naprawienie tego nie dodało zbyt wiele w sposobie uruchamiania.

Edycja 2: W jaki sposób idxmax() wie, którą kolumnę wybrać maksimum w swoim przykładowym kodzie?

0

Myślę, że użycie maski w celu zmniejszenia rozmiaru ramki danych, a następnie wykonanie bardziej zwięzłego zestawu operacji na tej mniejszej ramce bardzo pomoże. Znalezienie indeksy tylko do korzystania z tych, jak wyszukiwań jest zbędna - wystarczy znaleźć max/min wprost:

pat1 = re.compile('apple') 
pat2 = re.compile('sparta') 
mask = (df['string_col1'].str.contains(pat1)) & (df['string_col2'].str.contains(pat2)) 

result = df.loc[mask, 'value'] 
difference = result.max() - result.min() 
+0

Skąd się kurczecie, nie widzę? – denfromufa

+0

OP dokonuje ponownej filtracji całego DF dla każdej wykonywanej operacji. Raz wykonuję filtr, a następnie operuję na mniejszym zestawie wyników. Zobacz 'result = df.loc [maska, 'wartość']'. – jack6e

+0

To jest tania operacja, spróbuj ją zmierzyć. – denfromufa

2

można przyspieszyć logiczną porównanie o współczynnik 50 nie używając & ale scipy.logical_and() zamiast

a = pd.Series(sp.rand(10000) > 0.5) 
b = pd.Series(sp.rand(10000) > 0.5) 

%timeit sp.logical_and(a.values,b.values) 
100000 loops, best of 3: 6.31 µs per loop 

%timeit a & b 
1000 loops, best of 3: 390 µs per loop 
+0

Nie jest to funkcja scipy.logical_and(), ale zamiast używać .values ​​(). Spróbuj ponownie% timeit za pomocą a.values ​​i b.values, a otrzymasz ten sam czas. –

+0

naprawdę. Niespodziewany! Dzięki za wskazanie tego. Pytanie o procedurę, czy powinienem teraz usunąć tę odpowiedź? –