查找(仅)满足pandas DataFrame中给定条件的第一行

pet*_*ter 12 python pandas

我有一个df带有很长的随机正整数列的数据帧:

df = pd.DataFrame({'n': np.random.randint(1, 10, size = 10000)})
Run Code Online (Sandbox Code Playgroud)

我想确定列中第一个偶数的索引.一种方法是:

df[df.n % 2 == 0].iloc[0]
Run Code Online (Sandbox Code Playgroud)

但这涉及很多操作(生成指数f.n % 2 == 0,评估df这些指数,最后采取第一项)并且非常缓慢.像这样的循环要快得多:

for j in range(len(df)):
    if df.n.iloc[j] % 2 == 0:
        break
Run Code Online (Sandbox Code Playgroud)

也因为第一个结果可能在前几行.是否有任何pandas方法以相似的性能执行此操作?谢谢.

注意:这个条件(是一个偶数)只是一个例子. 我正在寻找适用于价值观的任何条件的解决方案,即快速单线替代:

df[ conditions on df.n ].iloc[0]
Run Code Online (Sandbox Code Playgroud)

pet*_*ter 7

为了好玩,我决定尝试几种可能性。我拿一个数据框:

MAX = 10**7
df = pd.DataFrame({'n': range(MAX)})
Run Code Online (Sandbox Code Playgroud)

(不是随机的这段时间)。我想找到的第一行这n >= N对于一些价值N。我计时了以下四个版本:

def getfirst_pandas(condition, df):
    return df[condition(df)].iloc[0]

def getfirst_iterrows_loop(condition, df):
    for index, row in df.iterrows():
        if condition(row):
            return index, row
    return None

def getfirst_for_loop(condition, df):
    for j in range(len(df)):
        if condition(df.iloc[j]):
            break
    return j

def getfirst_numpy_argmax(condition, df):
    array = df.as_matrix()
    imax  = np.argmax(condition(array))
    return df.index[imax]
Run Code Online (Sandbox Code Playgroud)

with N= 十的幂。当然,numpy(优化的 C)代码预计比forpython 中的循环更快,但我想看看Npython 循环的哪些值仍然可以。

我对线路进行计时:

getfirst_pandas(lambda x: x.n >= N, df)
getfirst_iterrows_loop(lambda x: x.n >= N, df)
getfirst_for_loop(lambda x: x.n >= N, df)
getfirst_numpy_argmax(lambda x: x >= N, df.n)
Run Code Online (Sandbox Code Playgroud)

N = 1, 10, 100, 1000, ...。这是性能的日志日志图:

图片

for只要“第一个真实位置”预计在开头,简单循环就可以了,但随后就变得糟糕了。这np.argmax是最安全的解决方案。

你可以从图表中,时间看到pandasargmax保持(几乎)恒定的,因为他们总是扫描整个阵列。拥有一个没有的nporpandas方法将是完美的。


Ant*_*vBR 5

做了一些计时,是的,使用发电机通常会给你更快的结果

df = pd.DataFrame({'n': np.random.randint(1, 10, size = 10000)})

%timeit df[df.n % 2 == 0].iloc[0]
%timeit df.iloc[next(k for k,v in df.iterrows() if v.n % 2 == 0)]
%timeit df.iloc[next(t[0] for t in df.itertuples() if t.n % 2 == 0)]
Run Code Online (Sandbox Code Playgroud)

我得到:

1000 loops, best of 3: 1.09 ms per loop
1000 loops, best of 3: 619 µs per loop # <-- iterrows generator
1000 loops, best of 3: 1.1 ms per loop
10000 loops, best of 3: 25 µs per loop # <--- your solution
Run Code Online (Sandbox Code Playgroud)

但是,当您将其放大时:

df = pd.DataFrame({'n': np.random.randint(1, 10, size = 1000000)})
Run Code Online (Sandbox Code Playgroud)

差异消失:

10 loops, best of 3: 40.5 ms per loop 
10 loops, best of 3: 40.7 ms per loop # <--- iterrows
10 loops, best of 3: 56.9 ms per loop
Run Code Online (Sandbox Code Playgroud)

您的解决方案是最快的,为什么不使用它呢?

for j in range(len(df)):
    if df.n.iloc[j] % 2 == 0:
        break
Run Code Online (Sandbox Code Playgroud)


Tho*_*ger 1

让您迭代行并在满意时停止的选项是使用 DataFrame.iterrows 它是 pandas 的行迭代器。

在这种情况下,你可以像这样实现它:

def get_first_row_with(condition, df):
    for index, row in df.iterrows():
        if condition(row):
            return index, row
    return None # Condition not met on any row in entire DataFrame
Run Code Online (Sandbox Code Playgroud)

然后,给定一个 DataFrame,例如:

df = pd.DataFrame({
                    'cats': [1,2,3,4], 
                    'dogs': [2,4,6,8]
                  }, 
                  index=['Alice', 'Bob', 'Charlie', 'Eve'])
Run Code Online (Sandbox Code Playgroud)

您可以将其用作:

def some_condition(row):
    return row.cats + row.dogs >= 7

index, row = get_first_row_with(some_condition, df)

# Use results however you like, e.g.:
print('{} is the first person to have at least 7 pets.'.format(index))
print('They have {} cats and {} dogs!'.format(row.cats, row.dogs))
Run Code Online (Sandbox Code Playgroud)

这会输出:

Charlie is the first person to have at least 7 pets.
They have 3 cats and 6 dogs!
Run Code Online (Sandbox Code Playgroud)