根据条件从数据框中随机采样而不丢失数据

Randomly sample from dataframe based on condition without losing data

我在 VBA 中一直这样做,但非常感谢有关如何以 python 方式执行此操作的建议。 我有一个 Excel sheet 有 8000 多行和 >25 列。我需要将某些行标记为需要人工仔细审查。基本上,对于 'Name' 列中的每个人,我们需要随机标记该人行的 10% 为需要审查。我们不想 lose/delete/suppress 其他行;他们需要保持可用——这就是为什么我认为 df.sample 行不通的原因。在实际数据中,会有20-30个唯一的Name,每个Name有300-400行。

到目前为止,我已经使用 pandas 将数据读入数据帧,做了一些与这个问题无关的事情,然后将数据帧写回到 Excel 文件中。作为其中的一部分,我还在数据框中创建了一个 'Random_No.' 列,认为这将是朝着我的目标迈出的有用一步(''基于我在 VBA 中的做法)。 .也许为每个名字调用类似 的东西。

可能有上百万种方法可以做到这一点,我一直在尝试各种方法,但如果您能就您认为最有效的方法提供一些建议,我将不胜感激。我似乎正在创建很多 'helper' 数据帧和系列以及 ArrayOfObjects...并且通常使一切变得比可能需要的更复杂。 有没有一种简单的方法可以在数据帧中做到这一点,而不是让新手Python一团糟?

这是数据的简化架构; 'Needs_review' 代表我正在尝试创建的列类型——同样,每个名称占 10%。一如既往地感谢任何 advice/direction.

使用副作用

df = pd.DataFrame({
    'Name': [f"Name_{i}" for i in np.random.randint(0,10,10000)],
    'Col1': np.random.randn(10000)})

need_review = []
df.groupby(['Name']).agg(
    lambda x: need_review.extend(
        np.random.choice(
            x.index, int(0.1*len(x.index)), replace=False).tolist())).unstack()
df['Needs_Review'] = False
df.loc[need_review, 'Needs_Review'] = True

print (df.groupby(['Name', 'Needs_Review'])['Needs_Review'].count())

输出:

Name    Needs_Review
Name_0  False           871
        True             96
Name_1  False           925
        True            102
Name_2  False           895
        True             99
Name_3  False           890
        True             98
Name_4  False           842
        True             93
Name_5  False           932
        True            103
Name_6  False           911
        True            101
Name_7  False           932
        True            103
Name_8  False           909
        True            101
Name_9  False           898
        True             99

我在这里回答我自己的问题,但也呼吁任何改进。也可能这对其他 Python 新手有用。

首先,再次感谢@mujjiga,他的解决方案在他生成的数据框中运行良好,但我发现当我将它用于我自己的数据时,我得到了非常不稳定的结果。真可惜,因为我的数据花了 2 到 3 秒,我认为这还不算太糟糕。 (话虽如此,VBA也花了那么长时间。)

所以我四处摸索,想出了一些可行的方法——但如果有任何改进建议,我将不胜感激。

首先,我试着继续用apply(lambda做点什么。这样就完成了工作,但耗时惊人的 23 秒:

percent = 10 #<-- CHANGE THIS AS NEEDED
def do_it(x):
        gk  = df[['Name','Random']].groupby('Name')
        tm = gk.get_group(x)
        lst = sorted(tm.Random)
        lim = lst[int(len(lst)*percent*0.01)]
        return lim   #here and below, 'lim' represents the number below which we select a row for randomisation
df['Needs_Review']=df.apply(lambda x: 'Randomised' if x['Random'] < do_it(x['Name']) else ' ',axis=1)

第二种方法。基本上这与我在 VBA 中所做的一样:循环遍历 ~25 个名称中的每一个,并在每个名称处循环遍历 8000 多行数据。它也完成了工作,令人惊讶的是(对我来说)速度更快,在 21 秒:

nms = df.Name.unique()  #get unique Names
df['Needs_Review']= ' '    #this adds empty column
percent = 10 #<-- CHANGE THIS AS NEEDED
percent = 100/percent
for i in range(0,len(nms)):  #iterate through names
        tm = gk.get_group(nms[i])
        lst = sorted(tm.Random)
        lim = lst[int( len(lst)*percent*0.01)] 
        for index, row in df.iterrows(): #now iterate through data rows
            if nms[i]==row['Name']:  #check if row matches Name
                if row['Random'] < lim:
                    df.loc[index,'Needs_Review'] = 'Randomised' 

第三种方法。但是我读到的关于 Python 的所有内容都说“避免循环——矢量化!”。所以我又摆弄了一些,想出了这个,使用 np.where。它有效——不到半秒!

nms = df.Name.unique()   #get unique Names
df['Needs_Review']= ''  #this adds empty column
percent = 10 #<-- CHANGE THIS AS NEEDED
for i in range(0,len(nms)):  #as you can see, I'm still iterating with a for-loop...
    tm  = df['Name']==nms[i]    
    tempy = df[tm] #this dataframe contains 1 Name at a time
    lim = sorted(tempy['Random'])[int(len(sorted(tempy['Random']))*percent*0.01)]
    #we want to flag any rows where Random =< lim

    
    fix = (df['Random']<=lim) & df['Name'].str.contains(nms[i]) #sets up a boolean condition
    keep = df['Needs_Review'] #this series represents the 'Needs_Review' column so far
    df['Needs_Review'] = np.where(fix, 'Randomised', keep) #replaces cells in 'Needs_Review' column if they meet the 'fix' boolean condition; otherwise existing values are re-inserted

嗯,这很酷。那我为什么还要缠着你们呢?因为我仍在使用“for 循环”,遍历我的 ~25 个名字中的每一个。我相信也有办法摆脱它。这个数据集的性能改进将是微乎其微的,但事情的原理让我很烦恼。 关于如何矢量化名称循环的任何想法?抱歉我在术语方面的笨拙。