2017-12-27 200 views
9

Biorąc pod uwagę DataFrame z wieloma kolumnami, w jaki sposób wybieramy wartości z poszczególnych kolumn po wierszu w celu utworzenia nowej serii?Pandy: Wybierz wartości z określonych kolumn w DataFrame według wiersza

df = pd.DataFrame({"A":[1,2,3,4], 
        "B":[10,20,30,40], 
        "C":[100,200,300,400]}) 
columns_to_select = ["B", "A", "A", "C"] 

Cel: [10, 2, 3, 400]

Jedną z metod, która działa jest użycie instrukcji stosowania.

df["cols"] = columns_to_select 
df.apply(lambda x: x[x.cols], axis=1) 

Niestety, nie jest to operacja wektoryzowana i zajmuje dużo czasu w dużym zbiorze danych. Wszelkie pomysły będą mile widziane.

Odpowiedz

10

Pandas approach:

In [22]: df['new'] = df.lookup(df.index, columns_to_select) 

In [23]: df 
Out[23]: 
    A B C new 
0 1 10 100 10 
1 2 20 200 2 
2 3 30 300 3 
3 4 40 400 400 
+1

jeden s za sobą. ;-) – Wen

+0

@Wen, tak, znam to uczucie - przepraszam :) – MaxU

+0

@MaxU To jest dokładnie to, czego szukałem. Dziękuję Ci! –

8

NumPy sposób

Oto wektorowy sposób NumPy użyciu advanced indexing -

# Extract array data 
In [10]: a = df.values 

# Get integer based column IDs 
In [11]: col_idx = np.searchsorted(df.columns, columns_to_select) 

# Use NumPy's advanced indexing to extract relevant elem per row 
In [12]: a[np.arange(len(col_idx)), col_idx] 
Out[12]: array([ 10, 2, 3, 400]) 

Jeśli nazwy kolumn df nie są sortowane, musimy użyć sorter argumentu z np.searchsorted. Kod wyodrębnić col_idx do takiej ogólnej df byłoby:

# https://stackoverflow.com/a/38489403/ @Divakar 
def column_index(df, query_cols): 
    cols = df.columns.values 
    sidx = np.argsort(cols) 
    return sidx[np.searchsorted(cols,query_cols,sorter=sidx)] 

Więc col_idx można by uzyskać jak tak -

col_idx = column_index(df, columns_to_select) 

Dalsza optymalizacja

Profilowanie to ujawniło, że wąskim gardłem przetwarzał ciągi znaków o numerze np.searchsorted, co jest typową słabością NumPy, ponieważ nie jest tak dobre w przypadku łańcuchów. Aby przezwyciężyć to i wykorzystując scenariusz szczególnego przypadku nazw kolumn składających się z pojedynczych liter, moglibyśmy szybko przekonwertować te wartości na liczby, a następnie przekazać je do searchsorted w celu znacznie szybszego przetwarzania.

Zatem zoptymalizowana wersja coraz identyfikatory kolumn całkowitych oparte na przypadku, gdy nazwy kolumn są pojedyncze litery i sortowane, byłoby -

def column_index_singlechar_sorted(df, query_cols): 
    c0 = np.fromstring(''.join(df.columns), dtype=np.uint8) 
    c1 = np.fromstring(''.join(query_cols), dtype=np.uint8) 
    return np.searchsorted(c0, c1) 

To daje nam zmodyfikowaną wersję rozwiązania jak tak -

Szybkość wczytywania -

In [149]: # Setup df with 26 uppercase column letters and many rows 
    ...: import string 
    ...: df = pd.DataFrame(np.random.randint(0,9,(1000000,26))) 
    ...: s = list(string.uppercase[:df.shape[1]]) 
    ...: df.columns = s 
    ...: idx = np.random.randint(0,df.shape[1],len(df)) 
    ...: columns_to_select = np.take(s, idx).tolist() 

# With df.lookup from @MaxU's soln 
In [150]: %timeit pd.Series(df.lookup(df.index, columns_to_select)) 
10 loops, best of 3: 76.7 ms per loop 

# With proposed one from this soln 
In [151]: %%timeit 
    ...: a = df.values 
    ...: col_idx = column_index_singlechar_sorted(df, columns_to_select) 
    ...: out = pd.Series(a[np.arange(len(col_idx)), col_idx]) 
10 loops, best of 3: 59 ms per loop 

Zważywszy, że df.lookup rozwiązuje dla ogólnego przypadku, to prawdopodobnie lepszy wybór, ale inne możliwe optymalizacje, jak pokazano w tym poście, mogą być również przydatne!