How to get the p-value between two groups after groupby in pandas?
Asked Answered
A

2

8

I am stuck on how to apply the custom function to calculate the p-value for two groups obtained from pandas groupby.

vocabulary

test = 0 ==> test
test = 1 ==> control

problem setup

import numpy as np
import pandas as pd
import scipy.stats as ss

np.random.seed(100)
N = 15
df = pd.DataFrame({'country': np.random.choice(['A','B','C'],N),
                   'test': np.random.choice([0,1], N),
                   'conversion': np.random.choice([0,1], N),
                   'sex': np.random.choice(['M','F'], N)

                  })


ans = df.groupby(['country','test'])['conversion'].agg(['size','mean']).unstack('test')
ans.columns = ['test_size','control_size','test_mean','control_mean']
         test_size  control_size  test_mean  control_mean
country                                                  
A                3             3   0.666667      0.666667
B                1             1   1.000000      1.000000
C                4             3   0.750000      1.000000

Question

Now I want to add two more columns to get the p-value between test and control group. But in my groupby I can only operate on one series at a time and I am not sure how to use two series to get the p-value.

Done so far:

def get_ttest(x,y):
    return stats.ttest_ind(x, y, equal_var=False).pvalue

pseudo code:

df.groupby(['country','test'])['conversion'].agg(
['size','mean', some_function_to_get_pvalue])

How to get the p-values columns?

Required Answer

I need the get the values for the column pvalue

         test_size  control_size  test_mean  control_mean  pvalue
country                                                  
A                3             3   0.666667      0.666667   ?
B                1             1   1.000000      1.000000   ?
C                4             3   0.750000      1.000000   ?
Algebraist answered 26/12, 2019 at 16:12 Comment(3)
Does this answer your question? Calculate pvalue from pandas DataFrameNectarous
TLDR: you can't do it with just one groupby. You'll need to write first aggregate by country+group to compute size, mean and variance (custom aggregation function required). Then, aggregate by country only to compute p-value (another custom aggregation function). The whole setup looks ugly, so perhaps just aggregating by country and doing the rest explicitly (i.e. filter dataframe by test/control, then merge and use .apply) would be more intuitive.Polysemy
@Polysemy Thanks for useful suggestions. I will look more into it.Algebraist
A
8

You can do this:

import numpy as np
import pandas as pd
import scipy.stats as stats

def get_ttest(x,y,sided=1):
    return stats.ttest_ind(x, y, equal_var=False).pvalue/sided

np.random.seed(100)
N = 15
df = pd.DataFrame({'country': np.random.choice(['A','B','C'],N),
                   'test': np.random.choice([0,1], N),
                   'conversion': np.random.choice([0,1], N),
                   'sex': np.random.choice(['M','F'], N)

                  })


col_groupby = 'country'
col_test_control = 'test'
col_effect = 'conversion'

a,b = df[col_test_control].unique()

df_pval = df.groupby([col_groupby,col_test_control])\
            [col_effect].agg(['size','mean']).unstack(col_test_control)

df_pval.columns = [f'group{a}_size',f'group{b}_size',
                   f'group{a}_mean',f'group{b}_mean']

df_pval['pvalue'] = df.groupby(col_groupby).apply(lambda dfx: get_ttest(
    dfx.loc[dfx[col_test_control] == a, col_effect],
    dfx.loc[dfx[col_test_control] == b, col_effect]))


df_pval.pipe(print)

Result

         test_size  control_size  test_mean  control_mean    pvalue
country                                                            
A                3             3   0.666667      0.666667  1.000000
B                1             1   1.000000      1.000000       NaN
C                4             3   0.750000      1.000000  0.391002

Test the result

# test for country C
c0 = df.loc[(df.country=='C') & (df.test==0),'conversion']
c1 = df.loc[(df.country=='C') & (df.test==1),'conversion']

pval = stats.ttest_ind(c0, c1, equal_var=False).pvalue
print(pval) # 0.39100221895577053
Algebraist answered 28/12, 2019 at 16:30 Comment(0)
A
3

pivot could be used to get the required transformation of the data.

def f(group):
    pvt_table = group.pivot(columns='test', values='conversion')
    return(stats.ttest_ind(pvt_table[0], pvt_table[1],
     equal_var=False, nan_policy='omit').pvalue)

grouped = df.groupby(['country'])['test','conversion']
grouped.apply(f)

#country
#A           1
#B          --
#C    0.391002
#dtype: object

Ardy answered 28/12, 2019 at 17:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.