Pandas:按行从DataFrame的特定列中选择值

Jas*_*hez 10 python indexing numpy pandas

给定具有多列的DataFrame,我们如何逐行选择特定值来创建新系列?

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"]
Run Code Online (Sandbox Code Playgroud)

目标: [10, 2, 3, 400]

一种有效的方法是使用apply语句.

df["cols"] = columns_to_select
df.apply(lambda x: x[x.cols], axis=1)
Run Code Online (Sandbox Code Playgroud)

不幸的是,这不是矢量化操作,并且在大型数据集上需要很长时间.任何想法,将不胜感激.

Max*_*axU 11

熊猫方法:

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
Run Code Online (Sandbox Code Playgroud)


Div*_*kar 8

NumPy的方式

这是一个矢量化的NumPy方式使用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])
Run Code Online (Sandbox Code Playgroud)

如果列名df没有排序,我们需要使用sorter参数np.searchsorted.提取col_idx这种泛型的代码df是:

# 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)]
Run Code Online (Sandbox Code Playgroud)

所以,col_idx会这样获得 -

col_idx = column_index(df, columns_to_select)
Run Code Online (Sandbox Code Playgroud)

进一步优化

剖析它显示瓶颈正在处理字符串np.searchsorted,通常的NumPy弱点是没有如此伟大的字符串.因此,为了克服这一点并使用列名称为单个字母的特殊情况,我们可以快速将它们转换为数字,然后将它们提供searchsorted给更快的处理.

因此,对于列名是单个字母并已排序的情况,获取基于整数的列ID的优化版本将是 -

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)
Run Code Online (Sandbox Code Playgroud)

这给了我们解决方案的修改版本,就像这样 -

a = df.values
col_idx = column_index_singlechar_sorted(df, columns_to_select)
out = pd.Series(a[np.arange(len(col_idx)), col_idx])
Run Code Online (Sandbox Code Playgroud)

计时 -

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
Run Code Online (Sandbox Code Playgroud)

鉴于df.lookup解决了一般情况,这可能是一个更好的选择,但是这篇文章中显示的其他可能的优化也可以很方便!