根据条件从数据框中随机采样而不丢失数据
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 个名字中的每一个。我相信也有办法摆脱它。这个数据集的性能改进将是微乎其微的,但事情的原理让我很烦恼。 关于如何矢量化名称循环的任何想法?抱歉我在术语方面的笨拙。
我在 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 个名字中的每一个。我相信也有办法摆脱它。这个数据集的性能改进将是微乎其微的,但事情的原理让我很烦恼。 关于如何矢量化名称循环的任何想法?抱歉我在术语方面的笨拙。