In [1]:
import pandas as pd
import numpy as np

import statsmodels.api as sm
import statsmodels.formula.api as smf
import scipy.stats as stats
from statsmodels.stats.multitest import multipletests

import tqdm

import matplotlib.pyplot as plt
import seaborn as sns

from skimage import filters, morphology, measure, color
from skimage.measure import regionprops
from tifffile import imread, imsave

import glob

from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler

from skimage import measure

from scipy.spatial.distance import pdist

import random
random.seed(42)

plt.rcParams['pdf.fonttype'] = 'truetype'
plt.rcParams['font.size'] = 20
plt.style.use('seaborn-v0_8-ticks')
In [2]:
color_palette = {'DMSO' : '#4d4d4d',
                 'ARYL_AG':'#F8766D', 'ARYL_INH':"#F8766D50",
                 'ANDR_AG':'#fccb17', 'ANDR_INH':'#C49A0050',  
                 "ESTR_AG": '#53B400', "ESTR_INH": '#53B40050', 
                 'GC_AG' : '#00C094', 'GC_INH' : '#00C09450',
                 'LX_AG' : '#00B6EB', 'LX_INH' : '#00B6EB50', 
                 'RET_AG' : '#A58AFF', 'RET_INH' : '#A58AFF50', 
                 'THYR_AG' : '#FB61D7', 'THYR_INH' : '#FB61D750'
}

import matplotlib.colors as mcolors

def hex_to_rgba(hex_color):
    """Convert hex color to RGBA. Supports 8-digit hex with alpha."""
    hex_color = hex_color.lstrip('#')
    if len(hex_color) == 6:
        return mcolors.to_rgba('#' + hex_color)
    elif len(hex_color) == 8:
        rgb = hex_color[:6]
        alpha = int(hex_color[6:], 16) / 255
        return (*mcolors.to_rgb('#' + rgb), alpha)
    else:
        raise ValueError(f"Invalid hex color: #{hex_color}")

def hex_to_rgba_string(hex_color):
    """Convert hex color to rgba() string for Seaborn."""
    hex_color = hex_color.lstrip('#')
    if len(hex_color) == 6:
        r, g, b = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
        return f'rgba({r}, {g}, {b}, 1.0)'
    elif len(hex_color) == 8:
        r, g, b = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
        a = int(hex_color[6:], 16) / 255
        return f'rgba({r}, {g}, {b}, {a:.3f})'
    else:
        raise ValueError(f"Invalid hex color: #{hex_color}")
    
# Convert all to RGBA
color_palette_rgba = {k: hex_to_rgba(v) for k, v in color_palette.items()}
color_palette_rgba_str = {k: hex_to_rgba_string(v) for k, v in color_palette.items()}

color_palette_expanded = {'DMSO_CTL08A': '#cacaca',
 'DMSO_CTL04E': '#4d4d4d',
 'ARYL_AG_CTL08A': '#fdd6d3',
 'ARYL_AG_CTL04E': '#F8766D',
 'ARYL_INH_CTL08A': '#fdd6d3',
 'ARYL_INH_CTL04E': '#F8766D50',
 'ANDR_AG_CTL08A': '#feefb9',
 'ANDR_AG_CTL04E': '#fccb17',
 'ANDR_INH_CTL08A': '#ede1b250',
 'ANDR_INH_CTL04E': '#C49A0050',
 'ESTR_AG_CTL08A': '#cbe8b2',
 'ESTR_AG_CTL04E': '#53B400',
 'ESTR_INH_CTL08A': '#cbe8b250',
 'ESTR_INH_CTL04E': '#53B40050',
 'GC_AG_CTL08A': '#b2ecdf',
 'GC_AG_CTL04E': '#00C094',
 'GC_INH_CTL08A': '#b2ecdf50',
 'GC_INH_CTL04E': '#00C09450',
 'LX_AG_CTL08A': '#b2e9f9',
 'LX_AG_CTL04E': '#00B6EB',
 'LX_INH_CTL08A': '#b2e9f950',
 'LX_INH_CTL04E': '#00B6EB50',
 'RET_AG_CTL08A': '#e4dcff',
 'RET_AG_CTL04E': '#A58AFF',
 'RET_INH_CTL08A': '#e4dcff50',
 'RET_INH_CTL04E': '#A58AFF50',
 'THYR_AG_CTL08A': '#fed0f3',
 'THYR_AG_CTL04E': '#FB61D7',
 'THYR_INH_CTL08A': '#fed0f350',
 'THYR_INH_CTL04E': '#FB61D750'}

line_palette = {
    'CTL08A': "#99cfd670",  # azzurro pastello
    'CTL04E': "#f8c88a70",  # arancio pastello
}
In [3]:
labels = list(color_palette.keys())
handles = [plt.Line2D([0], [0], color=color_palette[label], lw=8) for label in labels]

fig, ax = plt.subplots(figsize=(8, 2))
legend = ax.legend(
    handles, labels, loc='center', ncol=int(np.ceil(len(labels)/2)), frameon=False,
    bbox_to_anchor=(0.5, 0.5)
)
ax.axis('off')
plt.tight_layout()

plt.savefig('../../../../endpoints_figures/color_palette_legend.pdf', dpi=300, bbox_inches='tight')
/tmp/ipykernel_1121090/191618692.py:10: UserWarning: Tight layout not applied. The left and right margins cannot be made large enough to accommodate all axes decorations.
  plt.tight_layout()

Load manual checks¶

In [4]:
blank_mix1 = glob.glob('/group/testa/Project/EndPoints/TPSSU/analysis/single_tif_scanregion/*0136*.tif')
blank_mix1 = [f.split('/')[-1].split('.')[0] for f in blank_mix1]
blank_mix2 = glob.glob('/group/testa/Project/EndPoints/TPSSU/analysis/single_tif_scanregion/*0145*.tif')
blank_mix2 = [f.split('/')[-1].split('.')[0] for f in blank_mix2]
In [5]:
check_qc_ch1 = pd.read_csv('../../preprocessing/tables/Treshold_QC_check - Channel1_no_dup.csv')
check_qc_ch2 = pd.read_csv('../../preprocessing/tables/Treshold_QC_check - Channel2_no_dup.csv')
In [6]:
check_qc_ch1['general QC (keep or not)'] = check_qc_ch1['general QC (keep or not)'].str.upper()
check_qc_ch1['general QC (keep or not)'] = check_qc_ch1['general QC (keep or not)'].replace({'BETTER NOT': 'LIMIT', 'MAYBE': 'LIMIT', 'YES?': 'LIMIT', 'SUS': 'LIMIT', 'NOT SURE': 'LIMIT', 'YES ': 'YES', 'YES - 2 CBO': 'YES'})
check_qc_ch1['general QC (keep or not)'].value_counts().index
Out[6]:
Index(['YES', 'LIMIT', 'NO'], dtype='object', name='general QC (keep or not)')
In [7]:
check_qc_ch1['Reference Image Report'] = check_qc_ch1['Reference Image Report'].str.replace('.pdf', '')
In [8]:
check_qc_ch1['Reference Image Report'].value_counts()
Out[8]:
Reference Image Report
20240424_manuel_lessi_0148_ScanRegion1_rep_1_channel_1    1
20240424_manuel_lessi_0145_ScanRegion2_rep_2_channel_1    1
20240424_manuel_lessi_0145_ScanRegion3_rep_0_channel_1    1
20240424_manuel_lessi_0145_ScanRegion3_rep_1_channel_1    1
20240424_manuel_lessi_0145_ScanRegion3_rep_2_channel_1    1
                                                         ..
20240424_manuel_lessi_0136_ScanRegion2_rep_1_channel_1    1
20240424_manuel_lessi_0136_ScanRegion2_rep_2_channel_1    1
20240424_manuel_lessi_0136_ScanRegion3_rep_0_channel_1    1
20240424_manuel_lessi_0136_ScanRegion3_rep_1_channel_1    1
20240424_manuel_lessi_0136_ScanRegion3_rep_2_channel_1    1
Name: count, Length: 153, dtype: int64
In [9]:
check_qc_ch2['general QC (keep or not)'] = check_qc_ch2['general QC (keep or not)'].str.upper()
check_qc_ch2['general QC (keep or not)'] = check_qc_ch2['general QC (keep or not)'].replace({'NOT SURE': 'LIMIT', '?': 'LIMIT', 'YES ': 'YES'})
check_qc_ch2['general QC (keep or not)'].value_counts().index
Out[9]:
Index(['YES', 'NO', 'LIMIT'], dtype='object', name='general QC (keep or not)')
In [10]:
check_qc_ch2['Reference Image Report'] = check_qc_ch2['Reference Image Report'].str.replace('.pdf', '')
In [11]:
check_qc_ch1['Reference Image Report'].value_counts()
Out[11]:
Reference Image Report
20240424_manuel_lessi_0148_ScanRegion1_rep_1_channel_1    1
20240424_manuel_lessi_0145_ScanRegion2_rep_2_channel_1    1
20240424_manuel_lessi_0145_ScanRegion3_rep_0_channel_1    1
20240424_manuel_lessi_0145_ScanRegion3_rep_1_channel_1    1
20240424_manuel_lessi_0145_ScanRegion3_rep_2_channel_1    1
                                                         ..
20240424_manuel_lessi_0136_ScanRegion2_rep_1_channel_1    1
20240424_manuel_lessi_0136_ScanRegion2_rep_2_channel_1    1
20240424_manuel_lessi_0136_ScanRegion3_rep_0_channel_1    1
20240424_manuel_lessi_0136_ScanRegion3_rep_1_channel_1    1
20240424_manuel_lessi_0136_ScanRegion3_rep_2_channel_1    1
Name: count, Length: 153, dtype: int64
In [12]:
check_qc_ch1['ImageName_ScanRegion_Rep'] = check_qc_ch1['Reference Image Report'].apply(lambda x: '_'.join(x.split('_')[:7]))
check_qc_ch2['ImageName_ScanRegion_Rep'] = check_qc_ch2['Reference Image Report'].apply(lambda x: '_'.join(x.split('_')[:7]))
In [13]:
to_keep_ch1 = check_qc_ch1[check_qc_ch1['general QC (keep or not)'].isin(['YES', 'LIMIT'])]
to_keep_ch2 = check_qc_ch2[check_qc_ch2['general QC (keep or not)'].isin(['YES', 'LIMIT'])]

len(set(to_keep_ch1['ImageName_ScanRegion_Rep']).intersection(set(to_keep_ch2['ImageName_ScanRegion_Rep'])))
Out[13]:
100
In [14]:
len(check_qc_ch1['ImageName_ScanRegion_Rep'])
Out[14]:
153
In [15]:
len(check_qc_ch2['ImageName_ScanRegion_Rep'])
Out[15]:
275
In [16]:
good_tissue_to_keep_limit = list(set(check_qc_ch1[check_qc_ch1['general QC (keep or not)'].isin(['YES', 'LIMIT'])]['ImageName_ScanRegion_Rep']).union(set(check_qc_ch2[check_qc_ch2['general QC (keep or not)'].isin(['YES', 'LIMIT'])]['ImageName_ScanRegion_Rep'])))
In [17]:
len(good_tissue_to_keep_limit)
Out[17]:
245
In [18]:
good_tissue_to_keep_yes = list(set(check_qc_ch1[check_qc_ch1['general QC (keep or not)'].isin(['YES'])]['ImageName_ScanRegion_Rep']).union(set(check_qc_ch2[check_qc_ch2['general QC (keep or not)'].isin(['YES'])]['ImageName_ScanRegion_Rep'])))
In [19]:
len(good_tissue_to_keep_yes)
Out[19]:
211

Load sample sheet¶

In [20]:
sample_sheet  = pd.read_csv('../../preprocessing/tables/ImageAnalysisSampleSheet.csv')
sample_sheet
Out[20]:
Path Scan Area Condition Line OPA Paraffin Block ID MIX
0 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion0 ESTR_AG CTL04E 12 29 1
1 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion1 ESTR_AG CTL08A 12 30 1
2 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion2 ESTR_INH CTL04E 12 31 1
3 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion3 ANDR_AG CTL04E 12 33 1
4 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion4 ESTR_INH CTL08A 12 32 1
5 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion5 ANDR_AG CTL08A 12 34 1
6 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion0 ANDR_ANT CTL04E 13 35 1
7 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion1 ARYL_INH CTL08A 13 40 1
8 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion2 ARYL_INH CTL04E 13 39 1
9 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion3 ARYL_AG CTL08A 13 38 1
10 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion4 ANDR_ANT CTL08A 13 36 1
11 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion5 ARYL_AG CTL04E 13 37 1
12 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion0 LX_AG CTL04E 14 41 1
13 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion1 GC_AG CTL08A 14 46 1
14 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion2 LX_AG CTL08A 14 42 1
15 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion3 LX_INH CTL04E 14 43 1
16 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion4 LX_INH CTL08A 14 44 1
17 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion0 GC_INH CTL04E 15 47 1
18 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion1 THYR_INH CTL04E 15 51 1
19 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion2 THYR_AG CTL08A 15 50 1
20 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion3 GC_INH CTL08A 15 48 1
21 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion4 THYR_AG CTL04E 15 49 1
22 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion5 THYR_INH CTL08A 15 52 1
23 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion4 DMSO CTL04E 16 57 1
24 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion5 DMSO CTL08A 16 58 1
25 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion4 RET_AG CTL08A 17 64 1
26 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion0 RET_INH CTL04E 18 65 1
27 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion2 RET_INH CTL08A 18 66 1
28 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion0 ESTR_AG CTL04E 12 29 2
29 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion1 ANDR_AG CTL08A 12 34 2
30 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion2 ESTR_AG CTL08A 12 30 2
31 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion3 ESTR_INH CTL04E 12 31 2
32 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion4 ANDR_AG CTL04E 12 33 2
33 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion5 ESTR_INH CTL08A 12 32 2
34 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion0 ANDR_INH CTL08A 13 36 2
35 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion1 ANDR_INH CTL04E 13 35 2
36 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion2 ARYL_INH CTL04E 13 39 2
37 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion3 ARYL_AG CTL08A 13 38 2
38 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion4 ARYL_INH CTL08A 13 40 2
39 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion5 ARYL_AG CTL04E 13 37 2
40 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion0 LX_AG CTL08A 14 42 2
41 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion1 LX_INH CTL08A 14 44 2
42 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion2 LX_AG CTL04E 14 41 2
43 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion3 LX_INH CTL04E 14 43 2
44 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion4 GC_AG CTL08A 14 46 2
45 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion0 GC_INH CTL08A 15 48 2
46 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion1 GC_INH CTL04E 15 47 2
47 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion2 THYR_INH CTL04E 15 51 2
48 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion3 THYR_AG CTL04E 15 49 2
49 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion4 THYR_INH CTL08A 15 52 2
50 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion5 THYR_AG CTL08A 15 50 2
51 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion2 DMSO CTL08A 16 58 2
52 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion3 DMSO CTL04E 16 57 2
53 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion2 RET_AG CTL08A 17 64 2
54 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion0 RET_INH CTL08A 18 66 2
55 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion1 RET_INH CTL04E 18 65 2
In [21]:
sample_sheet['ImageName'] = sample_sheet['Path'].apply(lambda x: x.split('\\')[-1].split('.')[0])
sample_sheet['ImageName_ScanRegion'] = sample_sheet['ImageName'] + '_' + sample_sheet['Scan Area']
sample_sheet['Condition_Line'] = sample_sheet['Condition'] + '_' + sample_sheet['Line ']
sample_sheet
Out[21]:
Path Scan Area Condition Line OPA Paraffin Block ID MIX ImageName ImageName_ScanRegion Condition_Line
0 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion0 ESTR_AG CTL04E 12 29 1 20240424_manuel_lessi_0128 20240424_manuel_lessi_0128_ScanRegion0 ESTR_AG_CTL04E
1 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion1 ESTR_AG CTL08A 12 30 1 20240424_manuel_lessi_0128 20240424_manuel_lessi_0128_ScanRegion1 ESTR_AG_CTL08A
2 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion2 ESTR_INH CTL04E 12 31 1 20240424_manuel_lessi_0128 20240424_manuel_lessi_0128_ScanRegion2 ESTR_INH_CTL04E
3 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion3 ANDR_AG CTL04E 12 33 1 20240424_manuel_lessi_0128 20240424_manuel_lessi_0128_ScanRegion3 ANDR_AG_CTL04E
4 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion4 ESTR_INH CTL08A 12 32 1 20240424_manuel_lessi_0128 20240424_manuel_lessi_0128_ScanRegion4 ESTR_INH_CTL08A
5 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion5 ANDR_AG CTL08A 12 34 1 20240424_manuel_lessi_0128 20240424_manuel_lessi_0128_ScanRegion5 ANDR_AG_CTL08A
6 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion0 ANDR_ANT CTL04E 13 35 1 20240424_manuel_lessi_0129 20240424_manuel_lessi_0129_ScanRegion0 ANDR_ANT_CTL04E
7 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion1 ARYL_INH CTL08A 13 40 1 20240424_manuel_lessi_0129 20240424_manuel_lessi_0129_ScanRegion1 ARYL_INH_CTL08A
8 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion2 ARYL_INH CTL04E 13 39 1 20240424_manuel_lessi_0129 20240424_manuel_lessi_0129_ScanRegion2 ARYL_INH_CTL04E
9 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion3 ARYL_AG CTL08A 13 38 1 20240424_manuel_lessi_0129 20240424_manuel_lessi_0129_ScanRegion3 ARYL_AG_CTL08A
10 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion4 ANDR_ANT CTL08A 13 36 1 20240424_manuel_lessi_0129 20240424_manuel_lessi_0129_ScanRegion4 ANDR_ANT_CTL08A
11 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion5 ARYL_AG CTL04E 13 37 1 20240424_manuel_lessi_0129 20240424_manuel_lessi_0129_ScanRegion5 ARYL_AG_CTL04E
12 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion0 LX_AG CTL04E 14 41 1 20240424_manuel_lessi_0130 20240424_manuel_lessi_0130_ScanRegion0 LX_AG_CTL04E
13 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion1 GC_AG CTL08A 14 46 1 20240424_manuel_lessi_0130 20240424_manuel_lessi_0130_ScanRegion1 GC_AG_CTL08A
14 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion2 LX_AG CTL08A 14 42 1 20240424_manuel_lessi_0130 20240424_manuel_lessi_0130_ScanRegion2 LX_AG_CTL08A
15 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion3 LX_INH CTL04E 14 43 1 20240424_manuel_lessi_0130 20240424_manuel_lessi_0130_ScanRegion3 LX_INH_CTL04E
16 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion4 LX_INH CTL08A 14 44 1 20240424_manuel_lessi_0130 20240424_manuel_lessi_0130_ScanRegion4 LX_INH_CTL08A
17 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion0 GC_INH CTL04E 15 47 1 20240424_manuel_lessi_0131 20240424_manuel_lessi_0131_ScanRegion0 GC_INH_CTL04E
18 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion1 THYR_INH CTL04E 15 51 1 20240424_manuel_lessi_0131 20240424_manuel_lessi_0131_ScanRegion1 THYR_INH_CTL04E
19 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion2 THYR_AG CTL08A 15 50 1 20240424_manuel_lessi_0131 20240424_manuel_lessi_0131_ScanRegion2 THYR_AG_CTL08A
20 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion3 GC_INH CTL08A 15 48 1 20240424_manuel_lessi_0131 20240424_manuel_lessi_0131_ScanRegion3 GC_INH_CTL08A
21 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion4 THYR_AG CTL04E 15 49 1 20240424_manuel_lessi_0131 20240424_manuel_lessi_0131_ScanRegion4 THYR_AG_CTL04E
22 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion5 THYR_INH CTL08A 15 52 1 20240424_manuel_lessi_0131 20240424_manuel_lessi_0131_ScanRegion5 THYR_INH_CTL08A
23 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion4 DMSO CTL04E 16 57 1 20240424_manuel_lessi_0132 20240424_manuel_lessi_0132_ScanRegion4 DMSO_CTL04E
24 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion5 DMSO CTL08A 16 58 1 20240424_manuel_lessi_0132 20240424_manuel_lessi_0132_ScanRegion5 DMSO_CTL08A
25 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion4 RET_AG CTL08A 17 64 1 20240424_manuel_lessi_0133 20240424_manuel_lessi_0133_ScanRegion4 RET_AG_CTL08A
26 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion0 RET_INH CTL04E 18 65 1 20240424_manuel_lessi_0134 20240424_manuel_lessi_0134_ScanRegion0 RET_INH_CTL04E
27 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion2 RET_INH CTL08A 18 66 1 20240424_manuel_lessi_0134 20240424_manuel_lessi_0134_ScanRegion2 RET_INH_CTL08A
28 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion0 ESTR_AG CTL04E 12 29 2 20240424_manuel_lessi_0137 20240424_manuel_lessi_0137_ScanRegion0 ESTR_AG_CTL04E
29 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion1 ANDR_AG CTL08A 12 34 2 20240424_manuel_lessi_0137 20240424_manuel_lessi_0137_ScanRegion1 ANDR_AG_CTL08A
30 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion2 ESTR_AG CTL08A 12 30 2 20240424_manuel_lessi_0137 20240424_manuel_lessi_0137_ScanRegion2 ESTR_AG_CTL08A
31 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion3 ESTR_INH CTL04E 12 31 2 20240424_manuel_lessi_0137 20240424_manuel_lessi_0137_ScanRegion3 ESTR_INH_CTL04E
32 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion4 ANDR_AG CTL04E 12 33 2 20240424_manuel_lessi_0137 20240424_manuel_lessi_0137_ScanRegion4 ANDR_AG_CTL04E
33 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion5 ESTR_INH CTL08A 12 32 2 20240424_manuel_lessi_0137 20240424_manuel_lessi_0137_ScanRegion5 ESTR_INH_CTL08A
34 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion0 ANDR_INH CTL08A 13 36 2 20240424_manuel_lessi_0138 20240424_manuel_lessi_0138_ScanRegion0 ANDR_INH_CTL08A
35 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion1 ANDR_INH CTL04E 13 35 2 20240424_manuel_lessi_0138 20240424_manuel_lessi_0138_ScanRegion1 ANDR_INH_CTL04E
36 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion2 ARYL_INH CTL04E 13 39 2 20240424_manuel_lessi_0138 20240424_manuel_lessi_0138_ScanRegion2 ARYL_INH_CTL04E
37 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion3 ARYL_AG CTL08A 13 38 2 20240424_manuel_lessi_0138 20240424_manuel_lessi_0138_ScanRegion3 ARYL_AG_CTL08A
38 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion4 ARYL_INH CTL08A 13 40 2 20240424_manuel_lessi_0138 20240424_manuel_lessi_0138_ScanRegion4 ARYL_INH_CTL08A
39 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion5 ARYL_AG CTL04E 13 37 2 20240424_manuel_lessi_0138 20240424_manuel_lessi_0138_ScanRegion5 ARYL_AG_CTL04E
40 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion0 LX_AG CTL08A 14 42 2 20240424_manuel_lessi_0139 20240424_manuel_lessi_0139_ScanRegion0 LX_AG_CTL08A
41 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion1 LX_INH CTL08A 14 44 2 20240424_manuel_lessi_0139 20240424_manuel_lessi_0139_ScanRegion1 LX_INH_CTL08A
42 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion2 LX_AG CTL04E 14 41 2 20240424_manuel_lessi_0139 20240424_manuel_lessi_0139_ScanRegion2 LX_AG_CTL04E
43 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion3 LX_INH CTL04E 14 43 2 20240424_manuel_lessi_0139 20240424_manuel_lessi_0139_ScanRegion3 LX_INH_CTL04E
44 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion4 GC_AG CTL08A 14 46 2 20240424_manuel_lessi_0139 20240424_manuel_lessi_0139_ScanRegion4 GC_AG_CTL08A
45 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion0 GC_INH CTL08A 15 48 2 20240424_manuel_lessi_0140 20240424_manuel_lessi_0140_ScanRegion0 GC_INH_CTL08A
46 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion1 GC_INH CTL04E 15 47 2 20240424_manuel_lessi_0140 20240424_manuel_lessi_0140_ScanRegion1 GC_INH_CTL04E
47 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion2 THYR_INH CTL04E 15 51 2 20240424_manuel_lessi_0140 20240424_manuel_lessi_0140_ScanRegion2 THYR_INH_CTL04E
48 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion3 THYR_AG CTL04E 15 49 2 20240424_manuel_lessi_0140 20240424_manuel_lessi_0140_ScanRegion3 THYR_AG_CTL04E
49 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion4 THYR_INH CTL08A 15 52 2 20240424_manuel_lessi_0140 20240424_manuel_lessi_0140_ScanRegion4 THYR_INH_CTL08A
50 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion5 THYR_AG CTL08A 15 50 2 20240424_manuel_lessi_0140 20240424_manuel_lessi_0140_ScanRegion5 THYR_AG_CTL08A
51 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion2 DMSO CTL08A 16 58 2 20240424_manuel_lessi_0141 20240424_manuel_lessi_0141_ScanRegion2 DMSO_CTL08A
52 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion3 DMSO CTL04E 16 57 2 20240424_manuel_lessi_0141 20240424_manuel_lessi_0141_ScanRegion3 DMSO_CTL04E
53 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion2 RET_AG CTL08A 17 64 2 20240424_manuel_lessi_0142 20240424_manuel_lessi_0142_ScanRegion2 RET_AG_CTL08A
54 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion0 RET_INH CTL08A 18 66 2 20240424_manuel_lessi_0143 20240424_manuel_lessi_0143_ScanRegion0 RET_INH_CTL08A
55 T:\Project\EndPoints\TPSSU\Pictures\20240424_m... ScanRegion1 RET_INH CTL04E 18 65 2 20240424_manuel_lessi_0143 20240424_manuel_lessi_0143_ScanRegion1 RET_INH_CTL04E
In [22]:
sample_dict= {i:j for i, j in zip(sample_sheet['ImageName_ScanRegion'], sample_sheet['Condition_Line'])}
In [23]:
mix_1_names = sample_sheet[sample_sheet['MIX'] == 1]['ImageName'].tolist()
mix_2_names = sample_sheet[sample_sheet['MIX'] == 2]['ImageName'].tolist()
In [24]:
hormonal_conditions = ['ESTR_AG', 'ESTR_INH', 'ANDR_AG', 'ANDR_INH', 'ARYL_INH',
       'ARYL_AG', 'LX_AG', 'GC_AG', 'LX_INH', 'GC_INH', 'THYR_INH',
       'THYR_AG', 'DMSO', 'RET_AG',
       'RET_INH']
hormonal_conditions.remove('DMSO')
hormonal_conditions.sort()
hormonal_conditions = ['DMSO'] + hormonal_conditions

Load measurements file names¶

In [25]:
measurements_files = glob.glob("/group/testa/Project/EndPoints/TPSSU/analysis/measurements/*.csv")
measurements_files_mix_1 = [f for f in measurements_files if any(name in f for name in mix_1_names)]
measurements_files_mix_2 = [f for f in measurements_files if any(name in f for name in mix_2_names)]
In [26]:
mask_file = pd.read_csv("/group/testa/Project/EndPoints/TPSSU/analysis/cyto_mask_measures.csv")
mask_file
Out[26]:
Unnamed: 0 area label intensity_mean intensity_min intensity_max
0 20240424_manuel_lessi_0133_ScanRegion4_rep_0_c... 2415914.0 1.0 8478.139486 5323.0 45060.0
1 20240424_manuel_lessi_0143_ScanRegion0_rep_1_c... 10287703.0 1.0 9564.326972 6720.0 42038.0
2 20240424_manuel_lessi_0133_ScanRegion2_rep_1_c... 3015477.0 1.0 11510.699631 5536.0 49132.0
3 20240424_manuel_lessi_0128_ScanRegion4_rep_1_c... 6843311.0 1.0 6301.567833 4239.0 34600.0
4 20240424_manuel_lessi_0141_ScanRegion0_rep_2_c... 23305924.0 1.0 5729.083352 3988.0 24114.0
... ... ... ... ... ... ...
278 20240424_manuel_lessi_0145_ScanRegion2_rep_1_c... 35809244.0 1.0 367.353397 340.0 2983.0
279 20240424_manuel_lessi_0134_ScanRegion6_rep_1_c... NaN NaN NaN NaN NaN
280 20240424_manuel_lessi_0144_ScanRegion2_rep_1_c... 6015146.0 1.0 7573.352427 4627.0 44177.0
281 20240424_manuel_lessi_0129_ScanRegion5_rep_1_c... 23404593.0 1.0 7714.037972 4597.0 65534.0
282 20240424_manuel_lessi_0145_ScanRegion0_rep_1_c... 10141827.0 1.0 388.299189 360.0 10460.0

283 rows × 6 columns

In [27]:
mask_file['OriginalImageName'] = mask_file['Unnamed: 0'].apply(lambda x: '_'.join(x.split('_')[0:4]))
mask_file['ImageName_ScanRegion'] = mask_file['Unnamed: 0'].apply(lambda x: '_'.join(x.split('_')[0:5]))
mask_file['ImageName_ScanRegion_Rep'] = mask_file['Unnamed: 0'].apply(lambda x: '_'.join(x.split('_')[0:7]))
mask_file
Out[27]:
Unnamed: 0 area label intensity_mean intensity_min intensity_max OriginalImageName ImageName_ScanRegion ImageName_ScanRegion_Rep
0 20240424_manuel_lessi_0133_ScanRegion4_rep_0_c... 2415914.0 1.0 8478.139486 5323.0 45060.0 20240424_manuel_lessi_0133 20240424_manuel_lessi_0133_ScanRegion4 20240424_manuel_lessi_0133_ScanRegion4_rep_0
1 20240424_manuel_lessi_0143_ScanRegion0_rep_1_c... 10287703.0 1.0 9564.326972 6720.0 42038.0 20240424_manuel_lessi_0143 20240424_manuel_lessi_0143_ScanRegion0 20240424_manuel_lessi_0143_ScanRegion0_rep_1
2 20240424_manuel_lessi_0133_ScanRegion2_rep_1_c... 3015477.0 1.0 11510.699631 5536.0 49132.0 20240424_manuel_lessi_0133 20240424_manuel_lessi_0133_ScanRegion2 20240424_manuel_lessi_0133_ScanRegion2_rep_1
3 20240424_manuel_lessi_0128_ScanRegion4_rep_1_c... 6843311.0 1.0 6301.567833 4239.0 34600.0 20240424_manuel_lessi_0128 20240424_manuel_lessi_0128_ScanRegion4 20240424_manuel_lessi_0128_ScanRegion4_rep_1
4 20240424_manuel_lessi_0141_ScanRegion0_rep_2_c... 23305924.0 1.0 5729.083352 3988.0 24114.0 20240424_manuel_lessi_0141 20240424_manuel_lessi_0141_ScanRegion0 20240424_manuel_lessi_0141_ScanRegion0_rep_2
... ... ... ... ... ... ... ... ... ...
278 20240424_manuel_lessi_0145_ScanRegion2_rep_1_c... 35809244.0 1.0 367.353397 340.0 2983.0 20240424_manuel_lessi_0145 20240424_manuel_lessi_0145_ScanRegion2 20240424_manuel_lessi_0145_ScanRegion2_rep_1
279 20240424_manuel_lessi_0134_ScanRegion6_rep_1_c... NaN NaN NaN NaN NaN 20240424_manuel_lessi_0134 20240424_manuel_lessi_0134_ScanRegion6 20240424_manuel_lessi_0134_ScanRegion6_rep_1
280 20240424_manuel_lessi_0144_ScanRegion2_rep_1_c... 6015146.0 1.0 7573.352427 4627.0 44177.0 20240424_manuel_lessi_0144 20240424_manuel_lessi_0144_ScanRegion2 20240424_manuel_lessi_0144_ScanRegion2_rep_1
281 20240424_manuel_lessi_0129_ScanRegion5_rep_1_c... 23404593.0 1.0 7714.037972 4597.0 65534.0 20240424_manuel_lessi_0129 20240424_manuel_lessi_0129_ScanRegion5 20240424_manuel_lessi_0129_ScanRegion5_rep_1
282 20240424_manuel_lessi_0145_ScanRegion0_rep_1_c... 10141827.0 1.0 388.299189 360.0 10460.0 20240424_manuel_lessi_0145 20240424_manuel_lessi_0145_ScanRegion0 20240424_manuel_lessi_0145_ScanRegion0_rep_1

283 rows × 9 columns

In [28]:
mask_file_df_mix_1 = mask_file[mask_file.OriginalImageName.isin(mix_1_names)]
mask_file_df_mix_1
Out[28]:
Unnamed: 0 area label intensity_mean intensity_min intensity_max OriginalImageName ImageName_ScanRegion ImageName_ScanRegion_Rep
0 20240424_manuel_lessi_0133_ScanRegion4_rep_0_c... 2415914.0 1.0 8478.139486 5323.0 45060.0 20240424_manuel_lessi_0133 20240424_manuel_lessi_0133_ScanRegion4 20240424_manuel_lessi_0133_ScanRegion4_rep_0
2 20240424_manuel_lessi_0133_ScanRegion2_rep_1_c... 3015477.0 1.0 11510.699631 5536.0 49132.0 20240424_manuel_lessi_0133 20240424_manuel_lessi_0133_ScanRegion2 20240424_manuel_lessi_0133_ScanRegion2_rep_1
3 20240424_manuel_lessi_0128_ScanRegion4_rep_1_c... 6843311.0 1.0 6301.567833 4239.0 34600.0 20240424_manuel_lessi_0128 20240424_manuel_lessi_0128_ScanRegion4 20240424_manuel_lessi_0128_ScanRegion4_rep_1
9 20240424_manuel_lessi_0134_ScanRegion6_rep_0_c... 7147296.0 1.0 5035.728892 2796.0 20250.0 20240424_manuel_lessi_0134 20240424_manuel_lessi_0134_ScanRegion6 20240424_manuel_lessi_0134_ScanRegion6_rep_0
12 20240424_manuel_lessi_0130_ScanRegion2_rep_1_c... 5736151.0 1.0 3811.331029 2366.0 59754.0 20240424_manuel_lessi_0130 20240424_manuel_lessi_0130_ScanRegion2 20240424_manuel_lessi_0130_ScanRegion2_rep_1
... ... ... ... ... ... ... ... ... ...
272 20240424_manuel_lessi_0131_ScanRegion2_rep_1_c... 619563.0 1.0 7816.673183 4931.0 56017.0 20240424_manuel_lessi_0131 20240424_manuel_lessi_0131_ScanRegion2 20240424_manuel_lessi_0131_ScanRegion2_rep_1
274 20240424_manuel_lessi_0129_ScanRegion5_rep_2_c... 15482008.0 1.0 5670.165540 3481.0 56907.0 20240424_manuel_lessi_0129 20240424_manuel_lessi_0129_ScanRegion5 20240424_manuel_lessi_0129_ScanRegion5_rep_2
277 20240424_manuel_lessi_0133_ScanRegion0_rep_2_c... 10075425.0 1.0 4029.633461 2629.0 41430.0 20240424_manuel_lessi_0133 20240424_manuel_lessi_0133_ScanRegion0 20240424_manuel_lessi_0133_ScanRegion0_rep_2
279 20240424_manuel_lessi_0134_ScanRegion6_rep_1_c... NaN NaN NaN NaN NaN 20240424_manuel_lessi_0134 20240424_manuel_lessi_0134_ScanRegion6 20240424_manuel_lessi_0134_ScanRegion6_rep_1
281 20240424_manuel_lessi_0129_ScanRegion5_rep_1_c... 23404593.0 1.0 7714.037972 4597.0 65534.0 20240424_manuel_lessi_0129 20240424_manuel_lessi_0129_ScanRegion5 20240424_manuel_lessi_0129_ScanRegion5_rep_1

116 rows × 9 columns

In [29]:
mask_file_df_mix_2 = mask_file[mask_file.OriginalImageName.isin(mix_2_names)]
mask_file_df_mix_2
Out[29]:
Unnamed: 0 area label intensity_mean intensity_min intensity_max OriginalImageName ImageName_ScanRegion ImageName_ScanRegion_Rep
1 20240424_manuel_lessi_0143_ScanRegion0_rep_1_c... 10287703.0 1.0 9564.326972 6720.0 42038.0 20240424_manuel_lessi_0143 20240424_manuel_lessi_0143_ScanRegion0 20240424_manuel_lessi_0143_ScanRegion0_rep_1
4 20240424_manuel_lessi_0141_ScanRegion0_rep_2_c... 23305924.0 1.0 5729.083352 3988.0 24114.0 20240424_manuel_lessi_0141 20240424_manuel_lessi_0141_ScanRegion0 20240424_manuel_lessi_0141_ScanRegion0_rep_2
5 20240424_manuel_lessi_0138_ScanRegion3_rep_1_c... 6139706.0 1.0 10728.699008 7679.0 47373.0 20240424_manuel_lessi_0138 20240424_manuel_lessi_0138_ScanRegion3 20240424_manuel_lessi_0138_ScanRegion3_rep_1
6 20240424_manuel_lessi_0140_ScanRegion2_rep_0_c... 2887437.0 1.0 10582.573429 8073.0 32277.0 20240424_manuel_lessi_0140 20240424_manuel_lessi_0140_ScanRegion2 20240424_manuel_lessi_0140_ScanRegion2_rep_0
10 20240424_manuel_lessi_0142_ScanRegion3_rep_0_c... 18501332.0 1.0 8751.319145 6006.0 40819.0 20240424_manuel_lessi_0142 20240424_manuel_lessi_0142_ScanRegion3 20240424_manuel_lessi_0142_ScanRegion3_rep_0
... ... ... ... ... ... ... ... ... ...
266 20240424_manuel_lessi_0139_ScanRegion0_rep_2_c... 16192847.0 1.0 12416.619954 8449.0 52637.0 20240424_manuel_lessi_0139 20240424_manuel_lessi_0139_ScanRegion0 20240424_manuel_lessi_0139_ScanRegion0_rep_2
268 20240424_manuel_lessi_0140_ScanRegion4_rep_1_c... 10125138.0 1.0 6630.255008 3940.0 59013.0 20240424_manuel_lessi_0140 20240424_manuel_lessi_0140_ScanRegion4 20240424_manuel_lessi_0140_ScanRegion4_rep_1
271 20240424_manuel_lessi_0137_ScanRegion4_rep_1_c... 10843902.0 1.0 6203.065238 4195.0 41839.0 20240424_manuel_lessi_0137 20240424_manuel_lessi_0137_ScanRegion4 20240424_manuel_lessi_0137_ScanRegion4_rep_1
273 20240424_manuel_lessi_0142_ScanRegion2_rep_1_c... 2596930.0 1.0 4422.110883 3233.0 18355.0 20240424_manuel_lessi_0142 20240424_manuel_lessi_0142_ScanRegion2 20240424_manuel_lessi_0142_ScanRegion2_rep_1
276 20240424_manuel_lessi_0141_ScanRegion4_rep_1_c... 26594153.0 1.0 6924.010593 4440.0 41733.0 20240424_manuel_lessi_0141 20240424_manuel_lessi_0141_ScanRegion4 20240424_manuel_lessi_0141_ScanRegion4_rep_1

109 rows × 9 columns

Not filtered - only morphological features - PCA on single nuclei¶

In [30]:
def filter_nuclei(df):
    min_quantile = df['area'].quantile(0.01)
    max_quantile = df['area'].quantile(0.99)
    df = df[(df['area'] > min_quantile) & (df['area'] < max_quantile)]
    return df
In [31]:
tot_df_mix1_ch1 = pd.read_csv("/group/testa/Project/EndPoints/TPSSU/analysis/measurements_mix_1_ch1_CTIP2.csv")
tot_df_mix2_ch1 = pd.read_csv("/group/testa/Project/EndPoints/TPSSU/analysis/measurements_mix_2_ch1_NeuN.csv")

morphological_features = ['area', 'eccentricity', 'equivalent_diameter', 'extent',
                                            'major_axis_length', 'minor_axis_length', 'orientation', 'perimeter',
                                            'solidity']

tot_df_mix1_ch1['ImageName_ScanRegion_Rep'] = tot_df_mix1_ch1['ImageName'].apply(lambda x: '_'.join(x.split('_')[0:7]))

print(tot_df_mix1_ch1.shape)
tot_df_mix1_ch1 = tot_df_mix1_ch1.groupby('ImageName_ScanRegion_Rep').apply(filter_nuclei).reset_index(drop=True)
print(tot_df_mix1_ch1.shape)

tot_df_mix1_ch1['label_image'] = tot_df_mix1_ch1['label'].astype('str') + '_' + tot_df_mix1_ch1['ImageName_ScanRegion_Rep'].astype('str')
tot_df_mix1_ch1.set_index('label_image', inplace = True)
(4591259, 21)
/tmp/ipykernel_1121090/3325876105.py:11: DeprecationWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
  tot_df_mix1_ch1 = tot_df_mix1_ch1.groupby('ImageName_ScanRegion_Rep').apply(filter_nuclei).reset_index(drop=True)
(4496846, 21)
In [32]:
tot_df_mix1_ch1 = tot_df_mix1_ch1[morphological_features]
In [33]:
tot_df_mix2_ch1['ImageName_ScanRegion_Rep'] = tot_df_mix2_ch1['ImageName'].apply(lambda x: '_'.join(x.split('_')[0:7]))

print(tot_df_mix2_ch1.shape)
tot_df_mix2_ch1 = tot_df_mix2_ch1.groupby('ImageName_ScanRegion_Rep').apply(filter_nuclei).reset_index(drop=True)
print(tot_df_mix2_ch1.shape)

tot_df_mix2_ch1['label_image'] = tot_df_mix2_ch1['label'].astype('str') + '_' + tot_df_mix2_ch1['ImageName_ScanRegion_Rep'].astype('str')
tot_df_mix2_ch1.set_index('label_image', inplace = True)
(4788573, 21)
/tmp/ipykernel_1121090/531573534.py:4: DeprecationWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
  tot_df_mix2_ch1 = tot_df_mix2_ch1.groupby('ImageName_ScanRegion_Rep').apply(filter_nuclei).reset_index(drop=True)
(4690248, 21)
In [34]:
tot_df_mix2_ch1 = tot_df_mix2_ch1[morphological_features]
In [35]:
tot_df_morphological = pd.concat([tot_df_mix1_ch1, tot_df_mix2_ch1], axis=0)

tot_df_morphological['ImageName_ScanRegion_Rep'] = tot_df_morphological.reset_index()['label_image'].apply(lambda x: '_'.join(x.split('_')[1:8])).values
tot_df_morphological['ImageName_ScanRegion'] = tot_df_morphological.reset_index()['label_image'].apply(lambda x: '_'.join(x.split('_')[1:6])).values
tot_df_morphological['Condition_Line'] = tot_df_morphological['ImageName_ScanRegion'].apply(lambda x: sample_dict.get(x, 'Unknown'))
tot_df_morphological['Condition'] = tot_df_morphological['Condition_Line'].apply(lambda x: x.replace('_CTL04E', '').replace('_CTL08A', ''))
tot_df_morphological['Line'] = tot_df_morphological['Condition_Line'].apply(lambda x: x.split('_')[-1])

tot_df_morphological = tot_df_morphological[tot_df_morphological['Condition'].isin(hormonal_conditions)]
In [36]:
tot_df_morphological = tot_df_morphological[tot_df_morphological['ImageName_ScanRegion_Rep'].isin(good_tissue_to_keep_limit)]

tot_df_morphological = tot_df_morphological[tot_df_morphological["ImageName_ScanRegion_Rep"] != "20240424_manuel_lessi_0134_ScanRegion7_rep_1"] # outlier
tot_df_morphological = tot_df_morphological[tot_df_morphological['Condition'] != 'Unknown']  # filter out unknown conditions
In [37]:
features = tot_df_morphological[morphological_features]
In [38]:
len(tot_df_morphological["ImageName_ScanRegion_Rep"].unique())
Out[38]:
139
In [39]:
# Standardize the features
scaler = StandardScaler()
features_scaled = scaler.fit_transform(features)

# Perform PCA
pca = PCA()  
principal_components = pca.fit_transform(features_scaled)


# Add the principal components to the dataframe
for i in range(principal_components.shape[1]):
    tot_df_morphological[f'PC{i+1}'] = principal_components[:, i]


# Extract the most important features for PCA1 and PCA2
pca_components = pd.DataFrame(pca.components_, columns=features.columns, index=[f'PC{i+1}' for i in range(len(pca.components_))])
important_features_pca1 = pca_components.loc['PC1'].abs().sort_values(ascending=False).head(5)
important_features_pca2 = pca_components.loc['PC2'].abs().sort_values(ascending=False).head(5)
important_features_pca3 = pca_components.loc['PC3'].abs().sort_values(ascending=False).head(5)

print("Top 5 features contributing to PCA1:")
print(important_features_pca1)

print("\nTop 5 features contributing to PCA2:")
print(important_features_pca2)

print("\nTop 5 features contributing to PCA3:")
print(important_features_pca3)
Top 5 features contributing to PCA1:
equivalent_diameter    0.451671
perimeter              0.446028
area                   0.445225
major_axis_length      0.430577
minor_axis_length      0.424077
Name: PC1, dtype: float64

Top 5 features contributing to PCA2:
extent               0.637839
solidity             0.531573
eccentricity         0.499679
major_axis_length    0.179739
minor_axis_length    0.132653
Name: PC2, dtype: float64

Top 5 features contributing to PCA3:
orientation          0.999614
solidity             0.023722
eccentricity         0.010693
extent               0.009049
major_axis_length    0.002369
Name: PC3, dtype: float64
In [40]:
grouped_df = tot_df_morphological.groupby('ImageName_ScanRegion_Rep').mean(['PC1', 'PC2']).reset_index()
In [41]:
grouped_df['ImageName_ScanRegion'] = grouped_df['ImageName_ScanRegion_Rep'].apply(lambda x: '_'.join(x.split('_')[0:5])).values
grouped_df['Condition_Line'] = grouped_df['ImageName_ScanRegion'].apply(lambda x: sample_dict.get(x, 'Unknown'))
grouped_df['Condition'] = grouped_df['Condition_Line'].apply(lambda x: x.replace('_CTL04E', '').replace('_CTL08A', ''))
grouped_df['Line'] = grouped_df['Condition_Line'].apply(lambda x: x.split('_')[-1])
In [42]:
cmap_conditions = plt.matplotlib.colors.ListedColormap([color_palette[c] for c in grouped_df['Condition'].astype('category').cat.categories])

plt.scatter(
    grouped_df['PC1'], grouped_df['PC2'],
    #alpha=0.3,
    c=grouped_df['Condition'].astype('category').cat.codes,
    cmap=cmap_conditions
)

plt.savefig('../../../../endpoints_figures/endpoints_PCA.pdf', dpi = 300)
In [43]:
plt.scatter(
    grouped_df['PC2'], grouped_df['PC3'],
    #alpha=0.3,
    c=grouped_df['Condition'].astype('category').cat.codes,
    cmap=cmap_conditions,
)
plt.savefig('../../../../endpoints_figures/endpoints_PCA_PC2_PC3.pdf', dpi = 300)
In [44]:
fig, ax = plt.subplots(1, 2, figsize=(20, 5))

for i, cond in enumerate(hormonal_conditions):
    data = tot_df_morphological[(tot_df_morphological['Line'] == 'CTL08A') & 
                                 (tot_df_morphological['Condition'] == cond)]['PC1']
    box = ax[0].boxplot(data, positions=[i], patch_artist=True, widths=.8)
    for patch in box['boxes']:
        patch.set_facecolor(color_palette[cond])
        box['medians'][0].set_color('black')
    dmso_median = tot_df_morphological[(tot_df_morphological['Condition'] == 'DMSO') & 
                                       (tot_df_morphological['Line'] == 'CTL08A')]['PC1'].median()
    ax[0].axhline(dmso_median, color='red', linestyle='--', linewidth=.7, alpha=0.7)

for i, cond in enumerate(hormonal_conditions):
    data = tot_df_morphological[(tot_df_morphological['Line'] == 'CTL04E') & 
                                 (tot_df_morphological['Condition'] == cond)]['PC1']
    box = ax[1].boxplot(data, positions=[i], patch_artist=True, widths=.8)
    for patch in box['boxes']:
        patch.set_facecolor(color_palette[cond])
        box['medians'][0].set_color('black')
    dmso_median = tot_df_morphological[(tot_df_morphological['Condition'] == 'DMSO') & 
                                       (tot_df_morphological['Line'] == 'CTL04E')]['PC1'].median()
    ax[1].axhline(dmso_median, color='red', linestyle='--', linewidth=.7, alpha=0.7)

_ = ax[0].set_xticks(range(len(hormonal_conditions)))
_ = ax[0].set_xticklabels(hormonal_conditions, rotation=90)

_ = ax[1].set_xticks(range(len(hormonal_conditions)))
_ = ax[1].set_xticklabels(hormonal_conditions, rotation=90)

ax[0].set_title('CTL08A')
ax[1].set_title('CTL04E')

ax[0].set_ylabel('PC1')
ax[1].set_ylabel('PC1')

plt.savefig('../../../../endpoints_figures/endpoints_PC1_all_nuclei.pdf', dpi=300, bbox_inches='tight')
plt.savefig('../../../../endpoints_figures/endpoints_PC1_all_nuclei.png', dpi=300, bbox_inches='tight')
In [45]:
fig, ax = plt.subplots(1, 2, figsize=(20, 5))

for i, cond in enumerate(hormonal_conditions):
    data = tot_df_morphological[(tot_df_morphological['Line'] == 'CTL08A') & 
                                 (tot_df_morphological['Condition'] == cond)]['PC2']
    box = ax[0].boxplot(data, positions=[i], patch_artist=True, widths=.8)
    for patch in box['boxes']:
        patch.set_facecolor(color_palette[cond])
        box['medians'][0].set_color('black')
    dmso_median = tot_df_morphological[(tot_df_morphological['Condition'] == 'DMSO') & 
                                       (tot_df_morphological['Line'] == 'CTL08A')]['PC2'].median()
    ax[0].axhline(dmso_median, color='red', linestyle='--', linewidth=.7, alpha=0.7)

for i, cond in enumerate(hormonal_conditions):
    data = tot_df_morphological[(tot_df_morphological['Line'] == 'CTL04E') & 
                                 (tot_df_morphological['Condition'] == cond)]['PC2']
    box = ax[1].boxplot(data, positions=[i], patch_artist=True, widths=.8)
    for patch in box['boxes']:
        patch.set_facecolor(color_palette[cond])
        box['medians'][0].set_color('black')
    dmso_median = tot_df_morphological[(tot_df_morphological['Condition'] == 'DMSO') & 
                                       (tot_df_morphological['Line'] == 'CTL04E')]['PC2'].median()
    ax[1].axhline(dmso_median, color='red', linestyle='--', linewidth=.7, alpha=0.7)

_ = ax[0].set_xticks(range(len(hormonal_conditions)))
_ = ax[0].set_xticklabels(hormonal_conditions, rotation=90)

_ = ax[1].set_xticks(range(len(hormonal_conditions)))
_ = ax[1].set_xticklabels(hormonal_conditions, rotation=90)

ax[0].set_title('CTL08A')
ax[1].set_title('CTL04E')

ax[0].set_ylabel('PC2')
ax[1].set_ylabel('PC2')

plt.savefig('../../../../endpoints_figures/endpoints_PC2_all_nuclei.pdf', dpi=300, bbox_inches='tight')
plt.savefig('../../../../endpoints_figures/endpoints_PC2_all_nuclei.png', dpi=300, bbox_inches='tight')
In [46]:
fig, ax = plt.subplots(1, 2, figsize=(20, 5))

for i, cond in enumerate(hormonal_conditions):
    data = tot_df_morphological[(tot_df_morphological['Line'] == 'CTL08A') & 
                                 (tot_df_morphological['Condition'] == cond)]['PC3']
    box = ax[0].boxplot(data, positions=[i], patch_artist=True, widths=.8)
    for patch in box['boxes']:
        patch.set_facecolor(color_palette[cond])
        box['medians'][0].set_color('black')
    dmso_median = tot_df_morphological[(tot_df_morphological['Condition'] == 'DMSO') & 
                                       (tot_df_morphological['Line'] == 'CTL08A')]['PC3'].median()
    ax[0].axhline(dmso_median, color='red', linestyle='--', linewidth=.7, alpha=0.7)

for i, cond in enumerate(hormonal_conditions):
    data = tot_df_morphological[(tot_df_morphological['Line'] == 'CTL04E') & 
                                 (tot_df_morphological['Condition'] == cond)]['PC3']
    box = ax[1].boxplot(data, positions=[i], patch_artist=True, widths=.8)
    for patch in box['boxes']:
        patch.set_facecolor(color_palette[cond])
        box['medians'][0].set_color('black')
    dmso_median = tot_df_morphological[(tot_df_morphological['Condition'] == 'DMSO') & 
                                       (tot_df_morphological['Line'] == 'CTL04E')]['PC3'].median()
    ax[1].axhline(dmso_median, color='red', linestyle='--', linewidth=.7, alpha=0.7)

_ = ax[0].set_xticks(range(len(hormonal_conditions)))
_ = ax[0].set_xticklabels(hormonal_conditions, rotation=90)

_ = ax[1].set_xticks(range(len(hormonal_conditions)))
_ = ax[1].set_xticklabels(hormonal_conditions, rotation=90)

ax[0].set_title('CTL08A')
ax[1].set_title('CTL04E')

ax[0].set_ylabel('PC3')
ax[1].set_ylabel('PC3')

plt.savefig('../../../../endpoints_figures/endpoints_PC3_all_nuclei.pdf', dpi=300, bbox_inches='tight')
plt.savefig('../../../../endpoints_figures/endpoints_PC3_all_nuclei.png', dpi=300, bbox_inches='tight')

Grouped by mean - PC1¶

In [47]:
fig, ax = plt.subplots(1, 2, figsize=(20, 5))

for i, cond in enumerate(hormonal_conditions):
    data = grouped_df[(grouped_df['Line'] == 'CTL08A') & 
                                 (grouped_df['Condition'] == cond)]['PC1']
    box = ax[0].boxplot(data, positions=[i], patch_artist=True, widths=.8)
    for patch in box['boxes']:
        patch.set_facecolor(color_palette[cond])
        box['medians'][0].set_color('black')
        # Add stripplot for CTL08A (single points per box)

    dmso_median = grouped_df[(grouped_df['Condition'] == 'DMSO') & 
                                       (grouped_df['Line'] == 'CTL08A')]['PC1'].median()
    ax[0].axhline(dmso_median, color='red', linestyle='--', linewidth=.7, alpha=0.7)

sns.stripplot(
    data=grouped_df[(grouped_df['Line'] == 'CTL08A')],
    y='PC1', x='Condition', order=hormonal_conditions, color='black', alpha=0.5, jitter=False, ax=ax[0], size=8
)

for i, cond in enumerate(hormonal_conditions):
    data = grouped_df[(grouped_df['Line'] == 'CTL04E') & 
                                 (grouped_df['Condition'] == cond)]['PC1']
    box = ax[1].boxplot(data, positions=[i], patch_artist=True, widths=.8)
    for patch in box['boxes']:
        patch.set_facecolor(color_palette[cond])
        box['medians'][0].set_color('black')
    dmso_median = grouped_df[(grouped_df['Condition'] == 'DMSO') & 
                                       (grouped_df['Line'] == 'CTL04E')]['PC1'].median()
    ax[1].axhline(dmso_median, color='red', linestyle='--', linewidth=.7, alpha=0.7)

sns.stripplot(
    data=grouped_df[grouped_df['Line'] == 'CTL04E'],
    y='PC1', x='Condition', order=hormonal_conditions, color='black', alpha=0.5, jitter=False, ax=ax[1], size=8
)

_ = ax[0].set_xticks(range(len(hormonal_conditions)))
_ = ax[0].set_xticklabels(hormonal_conditions, rotation=90)

_ = ax[1].set_xticks(range(len(hormonal_conditions)))
_ = ax[1].set_xticklabels(hormonal_conditions, rotation=90)

ax[0].set_title('CTL08A')
ax[1].set_title('CTL04E')

ax[0].set_ylabel('PC1')
ax[1].set_ylabel('PC1')

plt.savefig('../../../../endpoints_figures/endpoints_PC1_grouped.pdf', dpi=300, bbox_inches='tight')
plt.savefig('../../../../endpoints_figures/endpoints_PC1_grouped.png', dpi=300, bbox_inches='tight')

Statistical test - PC1¶

CTL08A¶

In [48]:
grouped_df_CTL08A = grouped_df[grouped_df['Line'] == 'CTL08A']

to_test = {}
for i in grouped_df_CTL08A.Condition.unique():
    data = grouped_df_CTL08A[grouped_df_CTL08A['Condition'] == i]['PC1'].values
    to_test[i] = data    
    
results = stats.kruskal(*list(to_test.values()))
results
Out[48]:
KruskalResult(statistic=51.17330953153743, pvalue=3.87841286463078e-06)
In [49]:
DMSO = grouped_df_CTL08A[grouped_df_CTL08A['Condition'] == 'DMSO']['PC1'].values
results_MW_CTL08A_p_values = {}
statistics_MW_CTL08A = {}
for cond in grouped_df_CTL08A.Condition.unique():
    if cond != 'DMSO':
        data = grouped_df_CTL08A[grouped_df_CTL08A['Condition'] == cond]['PC1'].values
        results_MW_CTL08A_p_values[cond] = stats.mannwhitneyu(data, DMSO).pvalue
        statistics_MW_CTL08A[cond] = stats.mannwhitneyu(data, DMSO).statistic
        
pvals_CTL08A = list(results_MW_CTL08A_p_values.values())  # exclude intercept
corrected_pvals_CTL08A = multipletests(pvals_CTL08A, method='fdr_bh')[1]
corrected_pvals_CTL08A = {i:j for i, j in zip(results_MW_CTL08A_p_values.keys(), corrected_pvals_CTL08A)}

results_p_val = pd.DataFrame({'CTL08A': corrected_pvals_CTL08A.values()}, index = corrected_pvals_CTL08A.keys())
results_stat = pd.DataFrame({'CTL08A': statistics_MW_CTL08A.values()}, index = statistics_MW_CTL08A.keys())
In [50]:
DMSO = grouped_df_CTL08A[grouped_df_CTL08A['Condition'] == 'DMSO']['PC1'].values
diff_with_DMSO = {}
for cond in grouped_df_CTL08A.Condition.unique():
    if cond != 'DMSO':
        data = grouped_df_CTL08A[grouped_df_CTL08A['Condition'] == cond]['PC1'].values
        diff_with_DMSO[cond] = np.median(data) - np.median(DMSO)
In [51]:
results_diff = pd.DataFrame({'CTL08A': diff_with_DMSO.values()}, index = diff_with_DMSO.keys())

CTL04E¶

In [52]:
grouped_df_CTL04E = grouped_df[grouped_df['Line'] == 'CTL04E']

to_test = {}
for i in grouped_df_CTL04E.Condition.unique():
    data = grouped_df_CTL04E[grouped_df_CTL04E['Condition'] == i]['PC1'].values
    to_test[i] = data    
    
results = stats.kruskal(*list(to_test.values()))
results
Out[52]:
KruskalResult(statistic=42.534796404019005, pvalue=2.7062988752687628e-05)
In [53]:
DMSO = grouped_df_CTL04E[grouped_df_CTL04E['Condition'] == 'DMSO']['PC1'].values
results_MW_CTL04E_p_values = {}
statistics_MW_CTL04E = {}
for cond in grouped_df_CTL04E.Condition.unique():
    if cond != 'DMSO':
        data = grouped_df_CTL04E[grouped_df_CTL04E['Condition'] == cond]['PC1'].values
        results_MW_CTL04E_p_values[cond] = stats.mannwhitneyu(data, DMSO).pvalue
        statistics_MW_CTL04E[cond] = stats.mannwhitneyu(data, DMSO).statistic
        
pvals_CTL04E = list(results_MW_CTL04E_p_values.values())  # exclude intercept
corrected_pvals_CTL04E = multipletests(pvals_CTL04E, method='fdr_bh')[1]
corrected_pvals_CTL04E = {i:j for i, j in zip(results_MW_CTL04E_p_values.keys(), corrected_pvals_CTL04E)}

results_p_val['CTL04E'] = results_p_val.index.map(corrected_pvals_CTL04E)
results_stat['CTL04E'] = results_stat.index.map(statistics_MW_CTL04E)
In [54]:
DMSO = grouped_df_CTL04E[grouped_df_CTL04E['Condition'] == 'DMSO']['PC1'].values
diff_with_DMSO = {}
for cond in grouped_df_CTL04E.Condition.unique():
    if cond != 'DMSO':
        data = grouped_df_CTL04E[grouped_df_CTL04E['Condition'] == cond]['PC1'].values
        diff_with_DMSO[cond] = np.median(data) - np.median(DMSO)
In [55]:
results_diff['CTL04E'] = pd.DataFrame({'CTL04E': diff_with_DMSO.values()}, index = diff_with_DMSO.keys())
In [56]:
results_diff = np.round(results_diff, 2)
In [57]:
fig, ax = plt.subplots(figsize = (4,7))
#results_stat = results_stat.applymap(lambda x: f"{x:+.1f}%" if pd.notnull(x) else "")
#results_diff = results_diff[results_p_val < 0.05].fillna('')
sns.heatmap(results_p_val, annot=results_diff[results_p_val < 0.05].fillna(''), cmap='Greens_r', cbar_kws={'label': 'p-value'}, vmin=0, vmax=0.05, ax=ax, fmt='')
plt.title('Condition vs DMSO\n')
Out[57]:
Text(0.5, 1.0, 'Condition vs DMSO\n')

Grouped by mean - PC2¶

In [58]:
fig, ax = plt.subplots(1, 2, figsize=(20, 5))

for i, cond in enumerate(hormonal_conditions):
    data = grouped_df[(grouped_df['Line'] == 'CTL08A') & 
                                 (grouped_df['Condition'] == cond)]['PC2']
    box = ax[0].boxplot(data, positions=[i], patch_artist=True, widths=.8)
    for patch in box['boxes']:
        patch.set_facecolor(color_palette[cond])
        box['medians'][0].set_color('black')
        # Add stripplot for CTL08A (single points per box)

    dmso_median = grouped_df[(grouped_df['Condition'] == 'DMSO') & 
                                       (grouped_df['Line'] == 'CTL08A')]['PC2'].median()
    ax[0].axhline(dmso_median, color='red', linestyle='--', linewidth=.7, alpha=0.7)

sns.stripplot(
    data=grouped_df[(grouped_df['Line'] == 'CTL08A')],
    y='PC2', x='Condition', order=hormonal_conditions, color='black', alpha=0.5, jitter=False, ax=ax[0], size=8
)

for i, cond in enumerate(hormonal_conditions):
    data = grouped_df[(grouped_df['Line'] == 'CTL04E') & 
                                 (grouped_df['Condition'] == cond)]['PC2']
    box = ax[1].boxplot(data, positions=[i], patch_artist=True, widths=.8)
    for patch in box['boxes']:
        patch.set_facecolor(color_palette[cond])
        box['medians'][0].set_color('black')
    dmso_median = grouped_df[(grouped_df['Condition'] == 'DMSO') & 
                                       (grouped_df['Line'] == 'CTL04E')]['PC2'].median()
    ax[1].axhline(dmso_median, color='red', linestyle='--', linewidth=.7, alpha=0.7)

sns.stripplot(
    data=grouped_df[(grouped_df['Line'] == 'CTL04E')],
    y='PC2', x='Condition', order=hormonal_conditions, color='black', alpha=0.5, jitter=False, ax=ax[1], size=8
)

_ = ax[0].set_xticks(range(len(hormonal_conditions)))
_ = ax[0].set_xticklabels(hormonal_conditions, rotation=90)

_ = ax[1].set_xticks(range(len(hormonal_conditions)))
_ = ax[1].set_xticklabels(hormonal_conditions, rotation=90)

ax[0].set_title('CTL08A')
ax[1].set_title('CTL04E')

ax[0].set_ylabel('PC2')
ax[1].set_ylabel('PC2')

plt.savefig('../../../../endpoints_figures/endpoints_PC2_grouped.pdf', dpi=300, bbox_inches='tight')
plt.savefig('../../../../endpoints_figures/endpoints_PC2_grouped.png', dpi=300, bbox_inches='tight')

Statistical test - PC2¶

CTL08A¶

In [59]:
grouped_df_CTL08A = grouped_df[grouped_df['Line'] == 'CTL08A']

to_test = {}
for i in grouped_df_CTL08A.Condition.unique():
    data = grouped_df_CTL08A[grouped_df_CTL08A['Condition'] == i]['PC2'].values
    to_test[i] = data    
    
results = stats.kruskal(*list(to_test.values()))
results
Out[59]:
KruskalResult(statistic=57.05543654657589, pvalue=3.826203225448208e-07)
In [60]:
DMSO = grouped_df_CTL08A[grouped_df_CTL08A['Condition'] == 'DMSO']['PC2'].values
results_MW_CTL08A_p_values = {}
statistics_MW_CTL08A = {}
for cond in grouped_df_CTL08A.Condition.unique():
    if cond != 'DMSO':
        data = grouped_df_CTL08A[grouped_df_CTL08A['Condition'] == cond]['PC2'].values
        results_MW_CTL08A_p_values[cond] = stats.mannwhitneyu(data, DMSO).pvalue
        statistics_MW_CTL08A[cond] = stats.mannwhitneyu(data, DMSO).statistic
        
pvals_CTL08A = list(results_MW_CTL08A_p_values.values())  # exclude intercept
corrected_pvals_CTL08A = multipletests(pvals_CTL08A, method='fdr_bh')[1]
corrected_pvals_CTL08A = {i:j for i, j in zip(results_MW_CTL08A_p_values.keys(), corrected_pvals_CTL08A)}

results_p_val = pd.DataFrame({'CTL08A': corrected_pvals_CTL08A.values()}, index = corrected_pvals_CTL08A.keys())
results_stat = pd.DataFrame({'CTL08A': statistics_MW_CTL08A.values()}, index = statistics_MW_CTL08A.keys())
In [61]:
DMSO = grouped_df_CTL08A[grouped_df_CTL08A['Condition'] == 'DMSO']['PC2'].values
diff_with_DMSO = {}
for cond in grouped_df_CTL08A.Condition.unique():
    if cond != 'DMSO':
        data = grouped_df_CTL08A[grouped_df_CTL08A['Condition'] == cond]['PC2'].values
        diff_with_DMSO[cond] = np.median(data) - np.median(DMSO)
In [62]:
results_diff = pd.DataFrame({'CTL08A': diff_with_DMSO.values()}, index = diff_with_DMSO.keys())

CTL04E¶

In [63]:
grouped_df_CTL04E = grouped_df[grouped_df['Line'] == 'CTL04E']

to_test = {}
for i in grouped_df_CTL04E.Condition.unique():
    data = grouped_df_CTL04E[grouped_df_CTL04E['Condition'] == i]['PC2'].values
    to_test[i] = data    
    
results = stats.kruskal(*list(to_test.values()))
results
Out[63]:
KruskalResult(statistic=45.047435219460596, pvalue=1.0113944300737967e-05)
In [64]:
DMSO = grouped_df_CTL04E[grouped_df_CTL04E['Condition'] == 'DMSO']['PC2'].values
results_MW_CTL04E_p_values = {}
statistics_MW_CTL04E = {}
for cond in grouped_df_CTL04E.Condition.unique():
    if cond != 'DMSO':
        data = grouped_df_CTL04E[grouped_df_CTL04E['Condition'] == cond]['PC2'].values
        results_MW_CTL04E_p_values[cond] = stats.mannwhitneyu(data, DMSO).pvalue
        statistics_MW_CTL04E[cond] = stats.mannwhitneyu(data, DMSO).statistic
        
pvals_CTL04E = list(results_MW_CTL04E_p_values.values())  # exclude intercept
corrected_pvals_CTL04E = multipletests(pvals_CTL04E, method='fdr_bh')[1]
corrected_pvals_CTL04E = {i:j for i, j in zip(results_MW_CTL04E_p_values.keys(), corrected_pvals_CTL04E)}

results_p_val['CTL04E'] = results_p_val.index.map(corrected_pvals_CTL04E)
results_stat['CTL04E'] = results_stat.index.map(statistics_MW_CTL04E)
In [65]:
DMSO = grouped_df_CTL04E[grouped_df_CTL04E['Condition'] == 'DMSO']['PC2'].values
diff_with_DMSO = {}
for cond in grouped_df_CTL04E.Condition.unique():
    if cond != 'DMSO':
        data = grouped_df_CTL04E[grouped_df_CTL04E['Condition'] == cond]['PC2'].values
        diff_with_DMSO[cond] = np.median(data) - np.median(DMSO)
In [66]:
results_diff['CTL04E'] = pd.DataFrame({'CTL04E': diff_with_DMSO.values()}, index = diff_with_DMSO.keys())
In [67]:
results_diff = np.round(results_diff, 2)
In [68]:
fig, ax = plt.subplots(figsize = (4,7))
#results_stat = results_stat.applymap(lambda x: f"{x:+.1f}%" if pd.notnull(x) else "")
#results_diff = results_diff[results_p_val < 0.05].fillna('')
sns.heatmap(results_p_val, annot=results_diff[results_p_val < 0.05].fillna(''), cmap='Greens_r', cbar_kws={'label': 'p-value'}, vmin=0, vmax=0.05, ax=ax, fmt='')
plt.title('Condition vs DMSO\n')
Out[68]:
Text(0.5, 1.0, 'Condition vs DMSO\n')

Grouped by mean - PC3¶

In [69]:
fig, ax = plt.subplots(1, 2, figsize=(20, 5))

for i, cond in enumerate(hormonal_conditions):
    data = grouped_df[(grouped_df['Line'] == 'CTL08A') & 
                                 (grouped_df['Condition'] == cond)]['PC3']
    box = ax[0].boxplot(data, positions=[i], patch_artist=True, widths=.8)
    for patch in box['boxes']:
        patch.set_facecolor(color_palette[cond])
        box['medians'][0].set_color('black')
        # Add stripplot for CTL08A (single points per box)

    dmso_median = grouped_df[(grouped_df['Condition'] == 'DMSO') & 
                                       (grouped_df['Line'] == 'CTL08A')]['PC3'].median()
    ax[0].axhline(dmso_median, color='red', linestyle='--', linewidth=.7, alpha=0.7)

sns.stripplot(
    data=grouped_df[(grouped_df['Line'] == 'CTL08A')],
    y='PC3', x='Condition', order=hormonal_conditions, color='black', alpha=0.5, jitter=False, ax=ax[0], size=8
)

for i, cond in enumerate(hormonal_conditions):
    data = grouped_df[(grouped_df['Line'] == 'CTL04E') & 
                                 (grouped_df['Condition'] == cond)]['PC3']
    box = ax[1].boxplot(data, positions=[i], patch_artist=True, widths=.8)
    for patch in box['boxes']:
        patch.set_facecolor(color_palette[cond])
        box['medians'][0].set_color('black')
    dmso_median = grouped_df[(grouped_df['Condition'] == 'DMSO') & 
                                       (grouped_df['Line'] == 'CTL04E')]['PC3'].median()
    ax[1].axhline(dmso_median, color='red', linestyle='--', linewidth=.7, alpha=0.7)

sns.stripplot(
    data=grouped_df[(grouped_df['Line'] == 'CTL04E')],
    y='PC3', x='Condition', order=hormonal_conditions, color='black', alpha=0.5, jitter=False, ax=ax[1], size=8
)

_ = ax[0].set_xticks(range(len(hormonal_conditions)))
_ = ax[0].set_xticklabels(hormonal_conditions, rotation=90)

_ = ax[1].set_xticks(range(len(hormonal_conditions)))
_ = ax[1].set_xticklabels(hormonal_conditions, rotation=90)

ax[0].set_title('CTL08A')
ax[1].set_title('CTL04E')

ax[0].set_ylabel('PC3')
ax[1].set_ylabel('PC3')

plt.savefig('../../../../endpoints_figures/endpoints_PC3_grouped.pdf', dpi=300, bbox_inches='tight')
plt.savefig('../../../../endpoints_figures/endpoints_PC3_grouped.png', dpi=300, bbox_inches='tight')

Statistical test - PC3¶

CTL08A¶

In [70]:
grouped_df_CTL08A = grouped_df[grouped_df['Line'] == 'CTL08A']

to_test = {}
for i in grouped_df_CTL08A.Condition.unique():
    data = grouped_df_CTL08A[grouped_df_CTL08A['Condition'] == i]['PC3'].values
    to_test[i] = data    
    
results = stats.kruskal(*list(to_test.values()))
results
Out[70]:
KruskalResult(statistic=29.874099318403125, pvalue=0.00794236681106045)
In [71]:
DMSO = grouped_df_CTL08A[grouped_df_CTL08A['Condition'] == 'DMSO']['PC3'].values
results_MW_CTL08A_p_values = {}
statistics_MW_CTL08A = {}
for cond in grouped_df_CTL08A.Condition.unique():
    if cond != 'DMSO':
        data = grouped_df_CTL08A[grouped_df_CTL08A['Condition'] == cond]['PC3'].values
        results_MW_CTL08A_p_values[cond] = stats.mannwhitneyu(data, DMSO).pvalue
        statistics_MW_CTL08A[cond] = stats.mannwhitneyu(data, DMSO).statistic
        
pvals_CTL08A = list(results_MW_CTL08A_p_values.values())  # exclude intercept
corrected_pvals_CTL08A = multipletests(pvals_CTL08A, method='fdr_bh')[1]
corrected_pvals_CTL08A = {i:j for i, j in zip(results_MW_CTL08A_p_values.keys(), corrected_pvals_CTL08A)}

results_p_val = pd.DataFrame({'CTL08A': corrected_pvals_CTL08A.values()}, index = corrected_pvals_CTL08A.keys())
results_stat = pd.DataFrame({'CTL08A': statistics_MW_CTL08A.values()}, index = statistics_MW_CTL08A.keys())
In [72]:
DMSO = grouped_df_CTL08A[grouped_df_CTL08A['Condition'] == 'DMSO']['PC3'].values
diff_with_DMSO = {}
for cond in grouped_df_CTL08A.Condition.unique():
    if cond != 'DMSO':
        data = grouped_df_CTL08A[grouped_df_CTL08A['Condition'] == cond]['PC3'].values
        diff_with_DMSO[cond] = np.median(data) - np.median(DMSO)
In [73]:
results_diff = pd.DataFrame({'CTL08A': diff_with_DMSO.values()}, index = diff_with_DMSO.keys())

CTL04E¶

In [74]:
grouped_df_CTL04E = grouped_df[grouped_df['Line'] == 'CTL04E']

to_test = {}
for i in grouped_df_CTL04E.Condition.unique():
    data = grouped_df_CTL04E[grouped_df_CTL04E['Condition'] == i]['PC3'].values
    to_test[i] = data    
    
results = stats.kruskal(*list(to_test.values()))
results
Out[74]:
KruskalResult(statistic=27.127445795875133, pvalue=0.007407220324972986)
In [75]:
DMSO = grouped_df_CTL04E[grouped_df_CTL04E['Condition'] == 'DMSO']['PC3'].values
results_MW_CTL04E_p_values = {}
statistics_MW_CTL04E = {}
for cond in grouped_df_CTL04E.Condition.unique():
    if cond != 'DMSO':
        data = grouped_df_CTL04E[grouped_df_CTL04E['Condition'] == cond]['PC3'].values
        results_MW_CTL04E_p_values[cond] = stats.mannwhitneyu(data, DMSO).pvalue
        statistics_MW_CTL04E[cond] = stats.mannwhitneyu(data, DMSO).statistic
        
pvals_CTL04E = list(results_MW_CTL04E_p_values.values())  # exclude intercept
corrected_pvals_CTL04E = multipletests(pvals_CTL04E, method='fdr_bh')[1]
corrected_pvals_CTL04E = {i:j for i, j in zip(results_MW_CTL04E_p_values.keys(), corrected_pvals_CTL04E)}

results_p_val['CTL04E'] = results_p_val.index.map(corrected_pvals_CTL04E)
results_stat['CTL04E'] = results_stat.index.map(statistics_MW_CTL04E)
In [76]:
DMSO = grouped_df_CTL04E[grouped_df_CTL04E['Condition'] == 'DMSO']['PC3'].values
diff_with_DMSO = {}
for cond in grouped_df_CTL04E.Condition.unique():
    if cond != 'DMSO':
        data = grouped_df_CTL04E[grouped_df_CTL04E['Condition'] == cond]['PC3'].values
        diff_with_DMSO[cond] = np.median(data) - np.median(DMSO)
In [77]:
results_diff['CTL04E'] = pd.DataFrame({'CTL04E': diff_with_DMSO.values()}, index = diff_with_DMSO.keys())
In [78]:
results_diff = np.round(results_diff, 2)
In [79]:
fig, ax = plt.subplots(figsize = (4,7))
#results_stat = results_stat.applymap(lambda x: f"{x:+.1f}%" if pd.notnull(x) else "")
#results_diff = results_diff[results_p_val < 0.05].fillna('')
sns.heatmap(results_p_val, annot=results_diff[results_p_val < 0.05].fillna(''), cmap='Greens_r', cbar_kws={'label': 'p-value'}, vmin=0, vmax=0.05, ax=ax, fmt='')
plt.title('Condition vs DMSO\n')
Out[79]:
Text(0.5, 1.0, 'Condition vs DMSO\n')

Lollipop plots¶

In [80]:
important_features_pca1 = pca_components.loc['PC1'].sort_values(ascending=False)
import matplotlib.pyplot as plt

# Lollipop plot for important_features_pca1
plt.figure(figsize=(5, 6))
x = important_features_pca1.index
y = important_features_pca1.values
plt.stem(x, y, basefmt=" ")
plt.xticks(rotation=90)
plt.ylabel('Contribution to PC1')
plt.tight_layout()
plt.savefig('../../../../endpoints_figures/important_features_pca1.pdf', dpi=300, bbox_inches='tight')
In [81]:
important_features_pca2 = pca_components.loc['PC2'].sort_values(ascending=False)

plt.figure(figsize=(5, 6))
x = important_features_pca2.index
y = important_features_pca2.values
plt.stem(x, y, basefmt=" ")
plt.xticks(rotation=90)
plt.ylabel('Contribution to PC2')
plt.tight_layout()
plt.savefig('../../../../endpoints_figures/important_features_pca2.pdf', dpi=300, bbox_inches='tight')
In [82]:
important_features_pca3 = pca_components.loc['PC3'].sort_values(ascending=False)


plt.figure(figsize=(5, 6))
x = important_features_pca3.index
y = important_features_pca3.values
plt.stem(x, y, basefmt=" ")
plt.xticks(rotation=90)
plt.ylabel('Contribution to PC3')
plt.tight_layout()
plt.savefig('../../../../endpoints_figures/important_features_pca3.pdf', dpi=300, bbox_inches='tight')

Plot of equivalent diameter¶

Main contributor of variance for PC1

In [83]:
fig, ax = plt.subplots(1, 2, figsize=(20, 5))

for i, cond in enumerate(hormonal_conditions):
    data = tot_df_morphological[(tot_df_morphological['Line'] == 'CTL08A') & 
                                 (tot_df_morphological['Condition'] == cond)]['equivalent_diameter']
    box = ax[0].boxplot(data, positions=[i], patch_artist=True, widths=.8)
    for patch in box['boxes']:
        patch.set_facecolor(color_palette[cond])
        box['medians'][0].set_color('black')
    dmso_median = tot_df_morphological[(tot_df_morphological['Condition'] == 'DMSO') & 
                                       (tot_df_morphological['Line'] == 'CTL08A')]['equivalent_diameter'].median()
    ax[0].axhline(dmso_median, color='red', linestyle='--', linewidth=.7, alpha=0.7)

for i, cond in enumerate(hormonal_conditions):
    data = tot_df_morphological[(tot_df_morphological['Line'] == 'CTL04E') & 
                                 (tot_df_morphological['Condition'] == cond)]['equivalent_diameter']
    box = ax[1].boxplot(data, positions=[i], patch_artist=True, widths=.8)
    for patch in box['boxes']:
        patch.set_facecolor(color_palette[cond])
        box['medians'][0].set_color('black')
    dmso_median = tot_df_morphological[(tot_df_morphological['Condition'] == 'DMSO') & 
                                       (tot_df_morphological['Line'] == 'CTL04E')]['equivalent_diameter'].median()
    ax[1].axhline(dmso_median, color='red', linestyle='--', linewidth=.7, alpha=0.7)

_ = ax[0].set_xticks(range(len(hormonal_conditions)))
_ = ax[0].set_xticklabels(hormonal_conditions, rotation=90)

_ = ax[1].set_xticks(range(len(hormonal_conditions)))
_ = ax[1].set_xticklabels(hormonal_conditions, rotation=90)

ax[0].set_title('CTL08A')
ax[1].set_title('CTL04E')

ax[0].set_ylabel('equivalent_diameter')
ax[1].set_ylabel('equivalent_diameter')

plt.savefig('../../../../endpoints_figures/endpoints_equivalent_diameter_all_nuclei.pdf', dpi=300, bbox_inches='tight')
plt.savefig('../../../../endpoints_figures/endpoints_equivalent_diameter_all_nuclei.png', dpi=300, bbox_inches='tight')

Grouped by mean per organoid¶

In [84]:
fig, ax = plt.subplots(1, 2, figsize=(20, 5))

for i, cond in enumerate(hormonal_conditions):
    data = grouped_df[(grouped_df['Line'] == 'CTL08A') & 
                                 (grouped_df['Condition'] == cond)]['equivalent_diameter']
    box = ax[0].boxplot(data, positions=[i], patch_artist=True, widths=.8)
    for patch in box['boxes']:
        patch.set_facecolor(color_palette[cond])
        box['medians'][0].set_color('black')
        # Add stripplot for CTL08A (single points per box)

    dmso_median = grouped_df[(grouped_df['Condition'] == 'DMSO') & 
                                       (grouped_df['Line'] == 'CTL08A')]['equivalent_diameter'].median()
    ax[0].axhline(dmso_median, color='red', linestyle='--', linewidth=.7, alpha=0.7)

sns.stripplot(
    data=grouped_df[(grouped_df['Line'] == 'CTL08A')],
    y='equivalent_diameter', x='Condition', order=hormonal_conditions, color='black', alpha=0.5, jitter=False, ax=ax[0], size=8
)

for i, cond in enumerate(hormonal_conditions):
    data = grouped_df[(grouped_df['Line'] == 'CTL04E') & 
                                 (grouped_df['Condition'] == cond)]['equivalent_diameter']
    box = ax[1].boxplot(data, positions=[i], patch_artist=True, widths=.8)
    for patch in box['boxes']:
        patch.set_facecolor(color_palette[cond])
        box['medians'][0].set_color('black')
    dmso_median = grouped_df[(grouped_df['Condition'] == 'DMSO') & 
                                       (grouped_df['Line'] == 'CTL04E')]['equivalent_diameter'].median()
    ax[1].axhline(dmso_median, color='red', linestyle='--', linewidth=.7, alpha=0.7)

sns.stripplot(
    data=grouped_df[(grouped_df['Line'] == 'CTL04E')],
    y='equivalent_diameter', x='Condition', order=hormonal_conditions, color='black', alpha=0.5, jitter=False, ax=ax[1], size=8
)

_ = ax[0].set_xticks(range(len(hormonal_conditions)))
_ = ax[0].set_xticklabels(hormonal_conditions, rotation=90)

_ = ax[1].set_xticks(range(len(hormonal_conditions)))
_ = ax[1].set_xticklabels(hormonal_conditions, rotation=90)

ax[0].set_title('CTL08A')
ax[1].set_title('CTL04E')

ax[0].set_ylabel('equivalent_diameter')
ax[1].set_ylabel('equivalent_diameter')

plt.savefig('../../../../endpoints_figures/endpoints_equivalent_diameter_grouped.pdf', dpi=300, bbox_inches='tight')
plt.savefig('../../../../endpoints_figures/endpoints_equivalent_diameter_grouped.png', dpi=300, bbox_inches='tight')

Statistical test - Equivalent diameter¶

In [85]:
grouped_df_CTL08A = grouped_df[grouped_df['Line'] == 'CTL08A']
grouped_df_CTL08A['Condition'] = grouped_df_CTL08A['Condition'].astype('category')

model_CTL08A = smf.ols("equivalent_diameter ~ C(Condition, Treatment(reference='DMSO'))",
                data=grouped_df_CTL08A,
                ).fit()

print(model_CTL08A.summary())
                             OLS Regression Results                            
===============================================================================
Dep. Variable:     equivalent_diameter   R-squared:                       0.541
Model:                             OLS   Adj. R-squared:                  0.439
Method:                  Least Squares   F-statistic:                     5.298
Date:                 Tue, 28 Apr 2026   Prob (F-statistic):           1.69e-06
Time:                         15:53:39   Log-Likelihood:                -193.55
No. Observations:                   78   AIC:                             417.1
Df Residuals:                       63   BIC:                             452.5
Df Model:                           14                                         
Covariance Type:             nonrobust                                         
=========================================================================================================================
                                                            coef    std err          t      P>|t|      [0.025      0.975]
-------------------------------------------------------------------------------------------------------------------------
Intercept                                                19.1982      1.314     14.605      0.000      16.571      21.825
C(Condition, Treatment(reference='DMSO'))[T.ANDR_AG]      1.3612      1.859      0.732      0.467      -2.354       5.076
C(Condition, Treatment(reference='DMSO'))[T.ANDR_INH]     4.2478      2.277      1.866      0.067      -0.302       8.797
C(Condition, Treatment(reference='DMSO'))[T.ARYL_AG]      2.4017      1.697      1.415      0.162      -0.989       5.793
C(Condition, Treatment(reference='DMSO'))[T.ARYL_INH]     2.8381      1.859      1.527      0.132      -0.877       6.553
C(Condition, Treatment(reference='DMSO'))[T.ESTR_AG]      2.8453      2.078      1.369      0.176      -1.308       6.999
C(Condition, Treatment(reference='DMSO'))[T.ESTR_INH]     2.0426      2.078      0.983      0.329      -2.111       6.196
C(Condition, Treatment(reference='DMSO'))[T.GC_AG]        1.4172      2.078      0.682      0.498      -2.736       5.570
C(Condition, Treatment(reference='DMSO'))[T.GC_INH]       2.6384      1.950      1.353      0.181      -1.258       6.535
C(Condition, Treatment(reference='DMSO'))[T.LX_AG]        2.2393      1.859      1.205      0.233      -1.476       5.954
C(Condition, Treatment(reference='DMSO'))[T.LX_INH]       0.8054      2.277      0.354      0.725      -3.744       5.355
C(Condition, Treatment(reference='DMSO'))[T.RET_AG]      12.6892      1.859      6.826      0.000       8.974      16.404
C(Condition, Treatment(reference='DMSO'))[T.RET_INH]      0.2288      1.859      0.123      0.902      -3.486       3.944
C(Condition, Treatment(reference='DMSO'))[T.THYR_AG]      0.3718      1.950      0.191      0.849      -3.524       4.268
C(Condition, Treatment(reference='DMSO'))[T.THYR_INH]     0.3973      1.950      0.204      0.839      -3.499       4.293
==============================================================================
Omnibus:                       43.608   Durbin-Watson:                   1.275
Prob(Omnibus):                  0.000   Jarque-Bera (JB):              633.742
Skew:                           1.052   Prob(JB):                    2.42e-138
Kurtosis:                      16.805   Cond. No.                         15.0
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
/tmp/ipykernel_1121090/2352630096.py:2: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  grouped_df_CTL08A['Condition'] = grouped_df_CTL08A['Condition'].astype('category')
In [86]:
grouped_df_CTL04E = grouped_df[grouped_df['Line'] == 'CTL04E']
grouped_df_CTL04E['Condition'] = grouped_df_CTL04E['Condition'].astype('category')

model_CTL04E = smf.ols("equivalent_diameter ~ C(Condition, Treatment(reference='DMSO'))",
                data=grouped_df_CTL04E,
                ).fit()

print(model_CTL04E.summary())
                             OLS Regression Results                            
===============================================================================
Dep. Variable:     equivalent_diameter   R-squared:                       0.738
Model:                             OLS   Adj. R-squared:                  0.672
Method:                  Least Squares   F-statistic:                     11.25
Date:                 Tue, 28 Apr 2026   Prob (F-statistic):           3.14e-10
Time:                         15:53:39   Log-Likelihood:                -73.088
No. Observations:                   61   AIC:                             172.2
Df Residuals:                       48   BIC:                             199.6
Df Model:                           12                                         
Covariance Type:             nonrobust                                         
=========================================================================================================================
                                                            coef    std err          t      P>|t|      [0.025      0.975]
-------------------------------------------------------------------------------------------------------------------------
Intercept                                                20.3398      0.452     45.000      0.000      19.431      21.249
C(Condition, Treatment(reference='DMSO'))[T.ANDR_AG]      0.8830      0.639      1.381      0.174      -0.402       2.168
C(Condition, Treatment(reference='DMSO'))[T.ANDR_INH]     0.8536      0.690      1.236      0.222      -0.535       2.242
C(Condition, Treatment(reference='DMSO'))[T.ARYL_AG]     -1.4807      0.584     -2.537      0.014      -2.654      -0.307
C(Condition, Treatment(reference='DMSO'))[T.ARYL_INH]     1.6345      0.584      2.801      0.007       0.461       2.808
C(Condition, Treatment(reference='DMSO'))[T.ESTR_AG]     -0.0834      0.690     -0.121      0.904      -1.472       1.305
C(Condition, Treatment(reference='DMSO'))[T.ESTR_INH]    -1.1550      0.584     -1.979      0.054      -2.328       0.018
C(Condition, Treatment(reference='DMSO'))[T.GC_INH]      -1.9279      0.584     -3.304      0.002      -3.101      -0.755
C(Condition, Treatment(reference='DMSO'))[T.LX_AG]       -2.4619      0.606     -4.060      0.000      -3.681      -1.243
C(Condition, Treatment(reference='DMSO'))[T.LX_INH]      -1.8504      0.584     -3.171      0.003      -3.024      -0.677
C(Condition, Treatment(reference='DMSO'))[T.RET_INH]     -1.3852      0.639     -2.167      0.035      -2.670      -0.100
C(Condition, Treatment(reference='DMSO'))[T.THYR_AG]     -0.8752      0.584     -1.500      0.140      -2.048       0.298
C(Condition, Treatment(reference='DMSO'))[T.THYR_INH]     2.2845      0.783      2.918      0.005       0.710       3.859
==============================================================================
Omnibus:                        4.570   Durbin-Watson:                   2.574
Prob(Omnibus):                  0.102   Jarque-Bera (JB):                2.841
Skew:                          -0.334   Prob(JB):                        0.242
Kurtosis:                       2.180   Cond. No.                         15.1
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
/tmp/ipykernel_1121090/2302572593.py:2: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  grouped_df_CTL04E['Condition'] = grouped_df_CTL04E['Condition'].astype('category')
In [87]:
resid_CTL08A = model_CTL08A.resid

stats.probplot(resid_CTL08A, dist="norm", plot=plt)
plt.show()
In [88]:
resid_CTL04E = model_CTL04E.resid

stats.probplot(resid_CTL04E, dist="norm", plot=plt)
plt.show()
In [89]:
pvals_CTL08A = model_CTL08A.pvalues[1:]  # exclude intercept
corrected_pvals_CTL08A = multipletests(pvals_CTL08A, method='fdr_bh')[1]

df_results_CTL08A = pd.concat([model_CTL08A.params, pd.Series(corrected_pvals_CTL08A, index=model_CTL08A.params.index[1:])], axis = 1).iloc[1:]
df_results_CTL08A.columns = ['intercept', 'pvalue']
In [90]:
pvals_CTL04E = model_CTL04E.pvalues[1:]  # exclude intercept
corrected_pvals_CTL04E = multipletests(pvals_CTL04E, method='fdr_bh')[1]

df_results_CTL04E = pd.concat([model_CTL04E.params, pd.Series(corrected_pvals_CTL04E, index=model_CTL04E.params.index[1:])], axis = 1).iloc[1:]
df_results_CTL04E.columns = ['intercept', 'pvalue']
In [91]:
pvals = pd.concat([df_results_CTL08A['pvalue'], df_results_CTL04E['pvalue']], axis = 1)
pvals.columns = ['CTL08A', 'CTL04E']
pvals.index = [i.split('C(Condition, Treatment(reference=\'DMSO\'))[T.')[1].replace(']', '') for i in pvals.index]
pvals
Out[91]:
CTL08A CTL04E
ANDR_AG 6.969339e-01 0.208263
ANDR_INH 4.218924e-01 0.242570
ARYL_AG 4.218924e-01 0.028941
ARYL_INH 4.218924e-01 0.017565
ESTR_AG 4.218924e-01 0.904346
ESTR_INH 5.765536e-01 0.080280
GC_AG 6.969339e-01 NaN
GC_INH 4.218924e-01 0.010585
LX_AG 4.657286e-01 0.002164
LX_INH 9.024162e-01 0.010585
RET_AG 5.618575e-08 NaN
RET_INH 9.024162e-01 0.060399
THYR_AG 9.024162e-01 0.186927
THYR_INH 9.024162e-01 0.016037
In [92]:
intercepts = pd.concat([df_results_CTL08A['intercept'], df_results_CTL04E['intercept']], axis = 1)
intercepts.columns = ['CTL08A', 'CTL04E']
intercepts.index = [i.split('C(Condition, Treatment(reference=\'DMSO\'))[T.')[1].replace(']', '') for i in intercepts.index]

params = model_CTL08A.params
mean_DMSO = params["Intercept"]
        
intercepts['CTL08A'] = intercepts['CTL08A']/ mean_DMSO * 100

params = model_CTL04E.params
mean_DMSO = params["Intercept"]
intercepts['CTL04E'] = intercepts['CTL04E']/ mean_DMSO * 100
intercepts
Out[92]:
CTL08A CTL04E
ANDR_AG 7.090013 4.341349
ANDR_INH 22.125814 4.196708
ARYL_AG 12.510013 -7.279584
ARYL_INH 14.783145 8.036174
ESTR_AG 14.820896 -0.410092
ESTR_INH 10.639776 -5.678729
GC_AG 7.382001 NaN
GC_INH 13.743015 -9.478257
LX_AG 11.664044 -12.103826
LX_INH 4.195281 -9.097445
RET_AG 66.096060 NaN
RET_INH 1.192013 -6.810068
THYR_AG 1.936849 -4.302990
THYR_INH 2.069517 11.231696
In [93]:
fig, ax = plt.subplots(figsize = (4,7))
intercepts = intercepts.applymap(lambda x: f"{x:+.1f}%" if pd.notnull(x) else "")
intercepts = intercepts[pvals < 0.05].fillna('')
sns.heatmap(pvals, annot=intercepts, fmt='', cmap='Greens_r', cbar_kws={'label': 'p-value'}, vmin=0, vmax=0.05, ax = ax)
plt.title('Condition vs DMSO\n')
plt.savefig('../../../../endpoints_figures/endpoints_organoids_eq_diameter_OLS_test.png', dpi=300, bbox_inches='tight')
/tmp/ipykernel_1121090/3118655519.py:2: FutureWarning: DataFrame.applymap has been deprecated. Use DataFrame.map instead.
  intercepts = intercepts.applymap(lambda x: f"{x:+.1f}%" if pd.notnull(x) else "")

Plot of extent¶

Main contributor of variance for PC2

In [94]:
fig, ax = plt.subplots(1, 2, figsize=(20, 5))

for i, cond in enumerate(hormonal_conditions):
    data = tot_df_morphological[(tot_df_morphological['Line'] == 'CTL08A') & 
                                 (tot_df_morphological['Condition'] == cond)]['extent']
    box = ax[0].boxplot(data, positions=[i], patch_artist=True, widths=.8)
    for patch in box['boxes']:
        patch.set_facecolor(color_palette[cond])
        box['medians'][0].set_color('black')
    dmso_median = tot_df_morphological[(tot_df_morphological['Condition'] == 'DMSO') & 
                                       (tot_df_morphological['Line'] == 'CTL08A')]['extent'].median()
    ax[0].axhline(dmso_median, color='red', linestyle='--', linewidth=.7, alpha=0.7)

for i, cond in enumerate(hormonal_conditions):
    data = tot_df_morphological[(tot_df_morphological['Line'] == 'CTL04E') & 
                                 (tot_df_morphological['Condition'] == cond)]['extent']
    box = ax[1].boxplot(data, positions=[i], patch_artist=True, widths=.8)
    for patch in box['boxes']:
        patch.set_facecolor(color_palette[cond])
        box['medians'][0].set_color('black')
    dmso_median = tot_df_morphological[(tot_df_morphological['Condition'] == 'DMSO') & 
                                       (tot_df_morphological['Line'] == 'CTL04E')]['extent'].median()
    ax[1].axhline(dmso_median, color='red', linestyle='--', linewidth=.7, alpha=0.7)

_ = ax[0].set_xticks(range(len(hormonal_conditions)))
_ = ax[0].set_xticklabels(hormonal_conditions, rotation=90)

_ = ax[1].set_xticks(range(len(hormonal_conditions)))
_ = ax[1].set_xticklabels(hormonal_conditions, rotation=90)

ax[0].set_title('CTL08A')
ax[1].set_title('CTL04E')

ax[0].set_ylabel('extent')
ax[1].set_ylabel('extent')

plt.savefig('../../../../endpoints_figures/endpoints_extent_all_nuclei.pdf', dpi=300, bbox_inches='tight')
plt.savefig('../../../../endpoints_figures/endpoints_extent_all_nuclei.png', dpi=300, bbox_inches='tight')

Grouped by mean per organoid¶

In [95]:
fig, ax = plt.subplots(1, 2, figsize=(20, 5))

for i, cond in enumerate(hormonal_conditions):
    data = grouped_df[(grouped_df['Line'] == 'CTL08A') & 
                                 (grouped_df['Condition'] == cond)]['extent']
    box = ax[0].boxplot(data, positions=[i], patch_artist=True, widths=.8)
    for patch in box['boxes']:
        patch.set_facecolor(color_palette[cond])
        box['medians'][0].set_color('black')
        # Add stripplot for CTL08A (single points per box)

    dmso_median = grouped_df[(grouped_df['Condition'] == 'DMSO') & 
                                       (grouped_df['Line'] == 'CTL08A')]['extent'].median()
    ax[0].axhline(dmso_median, color='red', linestyle='--', linewidth=.7, alpha=0.7)

    sns.stripplot(
        data=grouped_df[(grouped_df['Line'] == 'CTL08A') & (grouped_df['Condition'] == cond)],
        y='extent', x='Condition', order=[cond], color='black', alpha=0.5, jitter=False, ax=ax[0], size=8
    )

for i, cond in enumerate(hormonal_conditions):
    data = grouped_df[(grouped_df['Line'] == 'CTL04E') & 
                                 (grouped_df['Condition'] == cond)]['extent']
    box = ax[1].boxplot(data, positions=[i], patch_artist=True, widths=.8)
    for patch in box['boxes']:
        patch.set_facecolor(color_palette[cond])
        box['medians'][0].set_color('black')
    dmso_median = grouped_df[(grouped_df['Condition'] == 'DMSO') & 
                                       (grouped_df['Line'] == 'CTL04E')]['extent'].median()
    ax[1].axhline(dmso_median, color='red', linestyle='--', linewidth=.7, alpha=0.7)

    sns.stripplot(
        data=grouped_df[(grouped_df['Line'] == 'CTL04E') & (grouped_df['Condition'] == cond)],
        y='extent', x='Condition', order=[cond], color='black', alpha=0.5, jitter=False, ax=ax[1], size=8
    )

_ = ax[0].set_xticks(range(len(hormonal_conditions)))
_ = ax[0].set_xticklabels(hormonal_conditions, rotation=90)

_ = ax[1].set_xticks(range(len(hormonal_conditions)))
_ = ax[1].set_xticklabels(hormonal_conditions, rotation=90)

ax[0].set_title('CTL08A')
ax[1].set_title('CTL04E')

ax[0].set_ylabel('extent')
ax[1].set_ylabel('extent')

plt.savefig('../../../../endpoints_figures/endpoints_extent_grouped.pdf', dpi=300, bbox_inches='tight')
plt.savefig('../../../../endpoints_figures/endpoints_extent_grouped.png', dpi=300, bbox_inches='tight')

Statistical test - Extent¶

In [96]:
grouped_df_CTL08A = grouped_df[grouped_df['Line'] == 'CTL08A']
grouped_df_CTL08A['Condition'] = grouped_df_CTL08A['Condition'].astype('category')

model_CTL08A = smf.ols("extent ~ C(Condition, Treatment(reference='DMSO'))",
                data=grouped_df_CTL08A,
                ).fit()

print(model_CTL08A.summary())
                            OLS Regression Results                            
==============================================================================
Dep. Variable:                 extent   R-squared:                       0.483
Model:                            OLS   Adj. R-squared:                  0.368
Method:                 Least Squares   F-statistic:                     4.206
Date:                Tue, 28 Apr 2026   Prob (F-statistic):           3.73e-05
Time:                        15:54:29   Log-Likelihood:                 300.72
No. Observations:                  78   AIC:                            -571.4
Df Residuals:                      63   BIC:                            -536.1
Df Model:                          14                                         
Covariance Type:            nonrobust                                         
=========================================================================================================================
                                                            coef    std err          t      P>|t|      [0.025      0.975]
-------------------------------------------------------------------------------------------------------------------------
Intercept                                                 0.7392      0.002    317.709      0.000       0.735       0.744
C(Condition, Treatment(reference='DMSO'))[T.ANDR_AG]     -0.0058      0.003     -1.757      0.084      -0.012       0.001
C(Condition, Treatment(reference='DMSO'))[T.ANDR_INH]    -0.0102      0.004     -2.528      0.014      -0.018      -0.002
C(Condition, Treatment(reference='DMSO'))[T.ARYL_AG]     -0.0088      0.003     -2.938      0.005      -0.015      -0.003
C(Condition, Treatment(reference='DMSO'))[T.ARYL_INH]    -0.0081      0.003     -2.464      0.016      -0.015      -0.002
C(Condition, Treatment(reference='DMSO'))[T.ESTR_AG]     -0.0137      0.004     -3.719      0.000      -0.021      -0.006
C(Condition, Treatment(reference='DMSO'))[T.ESTR_INH]     0.0004      0.004      0.099      0.922      -0.007       0.008
C(Condition, Treatment(reference='DMSO'))[T.GC_AG]        0.0008      0.004      0.219      0.828      -0.007       0.008
C(Condition, Treatment(reference='DMSO'))[T.GC_INH]      -0.0054      0.003     -1.577      0.120      -0.012       0.001
C(Condition, Treatment(reference='DMSO'))[T.LX_AG]       -0.0040      0.003     -1.211      0.230      -0.011       0.003
C(Condition, Treatment(reference='DMSO'))[T.LX_INH]      -0.0083      0.004     -2.051      0.044      -0.016      -0.000
C(Condition, Treatment(reference='DMSO'))[T.RET_AG]      -0.0114      0.003     -3.479      0.001      -0.018      -0.005
C(Condition, Treatment(reference='DMSO'))[T.RET_INH]      0.0041      0.003      1.234      0.222      -0.003       0.011
C(Condition, Treatment(reference='DMSO'))[T.THYR_AG]     -0.0069      0.003     -1.987      0.051      -0.014    3.79e-05
C(Condition, Treatment(reference='DMSO'))[T.THYR_INH]     0.0003      0.003      0.080      0.936      -0.007       0.007
==============================================================================
Omnibus:                       20.325   Durbin-Watson:                   2.053
Prob(Omnibus):                  0.000   Jarque-Bera (JB):              146.730
Skew:                          -0.019   Prob(JB):                     1.37e-32
Kurtosis:                       9.719   Cond. No.                         15.0
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
/tmp/ipykernel_1121090/3097047810.py:2: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  grouped_df_CTL08A['Condition'] = grouped_df_CTL08A['Condition'].astype('category')
In [97]:
grouped_df_CTL04E = grouped_df[grouped_df['Line'] == 'CTL04E']
grouped_df_CTL04E['Condition'] = grouped_df_CTL04E['Condition'].astype('category')

model_CTL04E = smf.ols("extent ~ C(Condition, Treatment(reference='DMSO'))",
                data=grouped_df_CTL04E,
                ).fit()

print(model_CTL04E.summary())
                            OLS Regression Results                            
==============================================================================
Dep. Variable:                 extent   R-squared:                       0.696
Model:                            OLS   Adj. R-squared:                  0.620
Method:                 Least Squares   F-statistic:                     9.153
Date:                Tue, 28 Apr 2026   Prob (F-statistic):           8.30e-09
Time:                        15:54:29   Log-Likelihood:                 265.75
No. Observations:                  61   AIC:                            -505.5
Df Residuals:                      48   BIC:                            -478.1
Df Model:                          12                                         
Covariance Type:            nonrobust                                         
=========================================================================================================================
                                                            coef    std err          t      P>|t|      [0.025      0.975]
-------------------------------------------------------------------------------------------------------------------------
Intercept                                                 0.7404      0.002    423.362      0.000       0.737       0.744
C(Condition, Treatment(reference='DMSO'))[T.ANDR_AG]     -0.0087      0.002     -3.509      0.001      -0.014      -0.004
C(Condition, Treatment(reference='DMSO'))[T.ANDR_INH]    -0.0073      0.003     -2.728      0.009      -0.013      -0.002
C(Condition, Treatment(reference='DMSO'))[T.ARYL_AG]     -0.0007      0.002     -0.289      0.774      -0.005       0.004
C(Condition, Treatment(reference='DMSO'))[T.ARYL_INH]    -0.0039      0.002     -1.739      0.088      -0.008       0.001
C(Condition, Treatment(reference='DMSO'))[T.ESTR_AG]      0.0028      0.003      1.055      0.297      -0.003       0.008
C(Condition, Treatment(reference='DMSO'))[T.ESTR_INH]     0.0085      0.002      3.756      0.000       0.004       0.013
C(Condition, Treatment(reference='DMSO'))[T.GC_INH]       0.0031      0.002      1.366      0.178      -0.001       0.008
C(Condition, Treatment(reference='DMSO'))[T.LX_AG]        0.0039      0.002      1.683      0.099      -0.001       0.009
C(Condition, Treatment(reference='DMSO'))[T.LX_INH]       0.0020      0.002      0.893      0.376      -0.003       0.007
C(Condition, Treatment(reference='DMSO'))[T.RET_INH]      0.0063      0.002      2.536      0.015       0.001       0.011
C(Condition, Treatment(reference='DMSO'))[T.THYR_AG]     -0.0025      0.002     -1.107      0.274      -0.007       0.002
C(Condition, Treatment(reference='DMSO'))[T.THYR_INH]     0.0006      0.003      0.197      0.844      -0.005       0.007
==============================================================================
Omnibus:                        0.005   Durbin-Watson:                   1.897
Prob(Omnibus):                  0.997   Jarque-Bera (JB):                0.138
Skew:                           0.010   Prob(JB):                        0.933
Kurtosis:                       2.768   Cond. No.                         15.1
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
/tmp/ipykernel_1121090/270499386.py:2: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  grouped_df_CTL04E['Condition'] = grouped_df_CTL04E['Condition'].astype('category')
In [98]:
resid_CTL08A = model_CTL08A.resid

stats.probplot(resid_CTL08A, dist="norm", plot=plt)
plt.show()
In [99]:
resid_CTL04E = model_CTL04E.resid

stats.probplot(resid_CTL04E, dist="norm", plot=plt)
plt.show()
In [100]:
pvals_CTL08A = model_CTL08A.pvalues[1:]  # exclude intercept
corrected_pvals_CTL08A = multipletests(pvals_CTL08A, method='fdr_bh')[1]

df_results_CTL08A = pd.concat([model_CTL08A.params, pd.Series(corrected_pvals_CTL08A, index=model_CTL08A.params.index[1:])], axis = 1).iloc[1:]
df_results_CTL08A.columns = ['intercept', 'pvalue']
In [101]:
pvals_CTL04E = model_CTL04E.pvalues[1:]  # exclude intercept
corrected_pvals_CTL04E = multipletests(pvals_CTL04E, method='fdr_bh')[1]

df_results_CTL04E = pd.concat([model_CTL04E.params, pd.Series(corrected_pvals_CTL04E, index=model_CTL04E.params.index[1:])], axis = 1).iloc[1:]
df_results_CTL04E.columns = ['intercept', 'pvalue']
In [102]:
pvals = pd.concat([df_results_CTL08A['pvalue'], df_results_CTL04E['pvalue']], axis = 1)
pvals.columns = ['CTL08A', 'CTL04E']
pvals.index = [i.split('C(Condition, Treatment(reference=\'DMSO\'))[T.')[1].replace(']', '') for i in pvals.index]
pvals
Out[102]:
CTL08A CTL04E
ANDR_AG 0.146588 0.005935
ANDR_INH 0.046107 0.035509
ARYL_AG 0.021496 0.844374
ARYL_INH 0.046107 0.197766
ESTR_AG 0.005981 0.395654
ESTR_INH 0.936429 0.005601
GC_AG 0.936429 NaN
GC_INH 0.186370 0.305775
LX_AG 0.293340 0.197766
LX_INH 0.102465 0.451261
RET_AG 0.006429 NaN
RET_INH 0.293340 0.043522
THYR_AG 0.102465 0.395654
THYR_INH 0.936429 0.844461
In [103]:
intercepts = pd.concat([df_results_CTL08A['intercept'], df_results_CTL04E['intercept']], axis = 1)
intercepts.columns = ['CTL08A', 'CTL04E']
intercepts.index = [i.split('C(Condition, Treatment(reference=\'DMSO\'))[T.')[1].replace(']', '') for i in intercepts.index]

params = model_CTL08A.params
mean_DMSO = params["Intercept"]
        
intercepts['CTL08A'] = intercepts['CTL08A']/ mean_DMSO * 100

params = model_CTL04E.params
mean_DMSO = params["Intercept"]
intercepts['CTL04E'] = intercepts['CTL04E']/ mean_DMSO * 100
intercepts
Out[103]:
CTL08A CTL04E
ANDR_AG -0.782122 -1.172067
ANDR_INH -1.378235 -0.984255
ARYL_AG -1.193967 -0.088054
ARYL_INH -1.096948 -0.530394
ESTR_AG -1.851045 0.380626
ESTR_INH 0.049108 1.145418
GC_AG 0.108898 NaN
GC_INH -0.736219 0.416484
LX_AG -0.538977 0.533323
LX_INH -1.117992 0.272461
RET_AG -1.548628 NaN
RET_INH 0.549067 0.847269
THYR_AG -0.927813 -0.337672
THYR_INH 0.037385 0.080701
In [104]:
fig, ax = plt.subplots(figsize = (4,7))
intercepts = intercepts.applymap(lambda x: f"{x:+.1f}%" if pd.notnull(x) else "")
intercepts = intercepts[pvals < 0.05].fillna('')
sns.heatmap(pvals, annot=intercepts, fmt='', cmap='Greens_r', cbar_kws={'label': 'p-value'}, vmin=0, vmax=0.05, ax = ax)
plt.title('Condition vs DMSO\n')
plt.savefig('../../../../endpoints_figures/endpoints_organoids_extent_OLS_test.png', dpi=300, bbox_inches='tight')
/tmp/ipykernel_1121090/1658638270.py:2: FutureWarning: DataFrame.applymap has been deprecated. Use DataFrame.map instead.
  intercepts = intercepts.applymap(lambda x: f"{x:+.1f}%" if pd.notnull(x) else "")

Plot of eccentricity¶

Main contributor of variance for PC2

In [105]:
fig, ax = plt.subplots(1, 2, figsize=(20, 5))

for i, cond in enumerate(hormonal_conditions):
    data = tot_df_morphological[(tot_df_morphological['Line'] == 'CTL08A') & 
                                 (tot_df_morphological['Condition'] == cond)]['eccentricity']
    box = ax[0].boxplot(data, positions=[i], patch_artist=True, widths=.8)
    for patch in box['boxes']:
        patch.set_facecolor(color_palette[cond])
        box['medians'][0].set_color('black')
    dmso_median = tot_df_morphological[(tot_df_morphological['Condition'] == 'DMSO') & 
                                       (tot_df_morphological['Line'] == 'CTL08A')]['eccentricity'].median()
    ax[0].axhline(dmso_median, color='red', linestyle='--', linewidth=.7, alpha=0.7)

for i, cond in enumerate(hormonal_conditions):
    data = tot_df_morphological[(tot_df_morphological['Line'] == 'CTL04E') & 
                                 (tot_df_morphological['Condition'] == cond)]['eccentricity']
    box = ax[1].boxplot(data, positions=[i], patch_artist=True, widths=.8)
    for patch in box['boxes']:
        patch.set_facecolor(color_palette[cond])
        box['medians'][0].set_color('black')
    dmso_median = tot_df_morphological[(tot_df_morphological['Condition'] == 'DMSO') & 
                                       (tot_df_morphological['Line'] == 'CTL04E')]['eccentricity'].median()
    ax[1].axhline(dmso_median, color='red', linestyle='--', linewidth=.7, alpha=0.7)

_ = ax[0].set_xticks(range(len(hormonal_conditions)))
_ = ax[0].set_xticklabels(hormonal_conditions, rotation=90)

_ = ax[1].set_xticks(range(len(hormonal_conditions)))
_ = ax[1].set_xticklabels(hormonal_conditions, rotation=90)

ax[0].set_title('CTL08A')
ax[1].set_title('CTL04E')

ax[0].set_ylabel('eccentricity')
ax[1].set_ylabel('eccentricity')

plt.savefig('../../../../endpoints_figures/endpoints_eccentricity_all_nuclei.pdf', dpi=300, bbox_inches='tight')
plt.savefig('../../../../endpoints_figures/endpoints_eccentricity_all_nuclei.png', dpi=300, bbox_inches='tight')

Grouped by mean per organoid¶

In [106]:
fig, ax = plt.subplots(1, 2, figsize=(20, 5))

for i, cond in enumerate(hormonal_conditions):
    data = grouped_df[(grouped_df['Line'] == 'CTL08A') & 
                                 (grouped_df['Condition'] == cond)]['eccentricity']
    box = ax[0].boxplot(data, positions=[i], patch_artist=True, widths=.8)
    for patch in box['boxes']:
        patch.set_facecolor(color_palette[cond])
        box['medians'][0].set_color('black')
        # Add stripplot for CTL08A (single points per box)

    dmso_median = grouped_df[(grouped_df['Condition'] == 'DMSO') & 
                                       (grouped_df['Line'] == 'CTL08A')]['eccentricity'].median()
    ax[0].axhline(dmso_median, color='red', linestyle='--', linewidth=.7, alpha=0.7)

    sns.stripplot(
        data=grouped_df[(grouped_df['Line'] == 'CTL08A') & (grouped_df['Condition'] == cond)],
        y='eccentricity', x='Condition', order=[cond], color='black', alpha=0.5, jitter=False, ax=ax[0], size=8
    )

for i, cond in enumerate(hormonal_conditions):
    data = grouped_df[(grouped_df['Line'] == 'CTL04E') & 
                                 (grouped_df['Condition'] == cond)]['eccentricity']
    box = ax[1].boxplot(data, positions=[i], patch_artist=True, widths=.8)
    for patch in box['boxes']:
        patch.set_facecolor(color_palette[cond])
        box['medians'][0].set_color('black')
    dmso_median = grouped_df[(grouped_df['Condition'] == 'DMSO') & 
                                       (grouped_df['Line'] == 'CTL04E')]['eccentricity'].median()
    ax[1].axhline(dmso_median, color='red', linestyle='--', linewidth=.7, alpha=0.7)

    sns.stripplot(
        data=grouped_df[(grouped_df['Line'] == 'CTL04E') & (grouped_df['Condition'] == cond)],
        y='eccentricity', x='Condition', order=[cond], color='black', alpha=0.5, jitter=False, ax=ax[1], size=8
    )

_ = ax[0].set_xticks(range(len(hormonal_conditions)))
_ = ax[0].set_xticklabels(hormonal_conditions, rotation=90)

_ = ax[1].set_xticks(range(len(hormonal_conditions)))
_ = ax[1].set_xticklabels(hormonal_conditions, rotation=90)

ax[0].set_title('CTL08A')
ax[1].set_title('CTL04E')

ax[0].set_ylabel('eccentricity')
ax[1].set_ylabel('eccentricity')

plt.savefig('../../../../endpoints_figures/endpoints_eccentricity_grouped.pdf', dpi=300, bbox_inches='tight')
plt.savefig('../../../../endpoints_figures/endpoints_eccentricity_grouped.png', dpi=300, bbox_inches='tight')

Statistical test - Eccentricity¶

In [107]:
grouped_df_CTL08A = grouped_df[grouped_df['Line'] == 'CTL08A']
grouped_df_CTL08A['Condition'] = grouped_df_CTL08A['Condition'].astype('category')

model_CTL08A = smf.ols("eccentricity ~ C(Condition, Treatment(reference='DMSO'))",
                data=grouped_df_CTL08A,
                ).fit()

print(model_CTL08A.summary())
                            OLS Regression Results                            
==============================================================================
Dep. Variable:           eccentricity   R-squared:                       0.622
Model:                            OLS   Adj. R-squared:                  0.537
Method:                 Least Squares   F-statistic:                     7.391
Date:                Tue, 28 Apr 2026   Prob (F-statistic):           8.35e-09
Time:                        15:55:15   Log-Likelihood:                 212.88
No. Observations:                  78   AIC:                            -395.8
Df Residuals:                      63   BIC:                            -360.4
Df Model:                          14                                         
Covariance Type:            nonrobust                                         
=========================================================================================================================
                                                            coef    std err          t      P>|t|      [0.025      0.975]
-------------------------------------------------------------------------------------------------------------------------
Intercept                                                 0.6177      0.007     86.093      0.000       0.603       0.632
C(Condition, Treatment(reference='DMSO'))[T.ANDR_AG]      0.0265      0.010      2.613      0.011       0.006       0.047
C(Condition, Treatment(reference='DMSO'))[T.ANDR_INH]     0.0500      0.012      4.025      0.000       0.025       0.075
C(Condition, Treatment(reference='DMSO'))[T.ARYL_AG]      0.0298      0.009      3.219      0.002       0.011       0.048
C(Condition, Treatment(reference='DMSO'))[T.ARYL_INH]     0.0440      0.010      4.340      0.000       0.024       0.064
C(Condition, Treatment(reference='DMSO'))[T.ESTR_AG]      0.0534      0.011      4.707      0.000       0.031       0.076
C(Condition, Treatment(reference='DMSO'))[T.ESTR_INH]     0.0066      0.011      0.582      0.563      -0.016       0.029
C(Condition, Treatment(reference='DMSO'))[T.GC_AG]       -0.0062      0.011     -0.550      0.584      -0.029       0.016
C(Condition, Treatment(reference='DMSO'))[T.GC_INH]       0.0116      0.011      1.086      0.281      -0.010       0.033
C(Condition, Treatment(reference='DMSO'))[T.LX_AG]        0.0247      0.010      2.438      0.018       0.004       0.045
C(Condition, Treatment(reference='DMSO'))[T.LX_INH]       0.0138      0.012      1.113      0.270      -0.011       0.039
C(Condition, Treatment(reference='DMSO'))[T.RET_AG]       0.0479      0.010      4.716      0.000       0.028       0.068
C(Condition, Treatment(reference='DMSO'))[T.RET_INH]     -0.0066      0.010     -0.655      0.515      -0.027       0.014
C(Condition, Treatment(reference='DMSO'))[T.THYR_AG]      0.0148      0.011      1.394      0.168      -0.006       0.036
C(Condition, Treatment(reference='DMSO'))[T.THYR_INH]    -0.0106      0.011     -0.998      0.322      -0.032       0.011
==============================================================================
Omnibus:                       11.233   Durbin-Watson:                   1.655
Prob(Omnibus):                  0.004   Jarque-Bera (JB):               20.877
Skew:                          -0.449   Prob(JB):                     2.93e-05
Kurtosis:                       5.370   Cond. No.                         15.0
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
/tmp/ipykernel_1121090/1012140979.py:2: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  grouped_df_CTL08A['Condition'] = grouped_df_CTL08A['Condition'].astype('category')
In [108]:
grouped_df_CTL04E = grouped_df[grouped_df['Line'] == 'CTL04E']
grouped_df_CTL04E['Condition'] = grouped_df_CTL04E['Condition'].astype('category')

model_CTL04E = smf.ols("eccentricity ~ C(Condition, Treatment(reference='DMSO'))",
                data=grouped_df_CTL04E,
                ).fit()

print(model_CTL04E.summary())
                            OLS Regression Results                            
==============================================================================
Dep. Variable:           eccentricity   R-squared:                       0.699
Model:                            OLS   Adj. R-squared:                  0.624
Method:                 Least Squares   F-statistic:                     9.303
Date:                Tue, 28 Apr 2026   Prob (F-statistic):           6.47e-09
Time:                        15:55:15   Log-Likelihood:                 183.87
No. Observations:                  61   AIC:                            -341.7
Df Residuals:                      48   BIC:                            -314.3
Df Model:                          12                                         
Covariance Type:            nonrobust                                         
=========================================================================================================================
                                                            coef    std err          t      P>|t|      [0.025      0.975]
-------------------------------------------------------------------------------------------------------------------------
Intercept                                                 0.6134      0.007     91.640      0.000       0.600       0.627
C(Condition, Treatment(reference='DMSO'))[T.ANDR_AG]      0.0365      0.009      3.855      0.000       0.017       0.056
C(Condition, Treatment(reference='DMSO'))[T.ANDR_INH]     0.0279      0.010      2.726      0.009       0.007       0.048
C(Condition, Treatment(reference='DMSO'))[T.ARYL_AG]      0.0062      0.009      0.722      0.474      -0.011       0.024
C(Condition, Treatment(reference='DMSO'))[T.ARYL_INH]     0.0093      0.009      1.072      0.289      -0.008       0.027
C(Condition, Treatment(reference='DMSO'))[T.ESTR_AG]     -0.0094      0.010     -0.922      0.361      -0.030       0.011
C(Condition, Treatment(reference='DMSO'))[T.ESTR_INH]    -0.0289      0.009     -3.346      0.002      -0.046      -0.012
C(Condition, Treatment(reference='DMSO'))[T.GC_INH]      -0.0231      0.009     -2.668      0.010      -0.040      -0.006
C(Condition, Treatment(reference='DMSO'))[T.LX_AG]       -0.0151      0.009     -1.678      0.100      -0.033       0.003
C(Condition, Treatment(reference='DMSO'))[T.LX_INH]      -0.0109      0.009     -1.261      0.213      -0.028       0.006
C(Condition, Treatment(reference='DMSO'))[T.RET_INH]     -0.0214      0.009     -2.266      0.028      -0.040      -0.002
C(Condition, Treatment(reference='DMSO'))[T.THYR_AG]      0.0056      0.009      0.652      0.517      -0.012       0.023
C(Condition, Treatment(reference='DMSO'))[T.THYR_INH]    -0.0029      0.012     -0.251      0.803      -0.026       0.020
==============================================================================
Omnibus:                        0.652   Durbin-Watson:                   2.177
Prob(Omnibus):                  0.722   Jarque-Bera (JB):                0.168
Skew:                          -0.022   Prob(JB):                        0.920
Kurtosis:                       3.253   Cond. No.                         15.1
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
/tmp/ipykernel_1121090/2137971027.py:2: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  grouped_df_CTL04E['Condition'] = grouped_df_CTL04E['Condition'].astype('category')
In [109]:
resid_CTL08A = model_CTL08A.resid

stats.probplot(resid_CTL08A, dist="norm", plot=plt)
plt.show()
In [110]:
resid_CTL04E = model_CTL04E.resid

stats.probplot(resid_CTL04E, dist="norm", plot=plt)
plt.show()
In [111]:
pvals_CTL08A = model_CTL08A.pvalues[1:]  # exclude intercept
corrected_pvals_CTL08A = multipletests(pvals_CTL08A, method='fdr_bh')[1]

df_results_CTL08A = pd.concat([model_CTL08A.params, pd.Series(corrected_pvals_CTL08A, index=model_CTL08A.params.index[1:])], axis = 1).iloc[1:]
df_results_CTL08A.columns = ['intercept', 'pvalue']
In [112]:
pvals_CTL04E = model_CTL04E.pvalues[1:]  # exclude intercept
corrected_pvals_CTL04E = multipletests(pvals_CTL04E, method='fdr_bh')[1]

df_results_CTL04E = pd.concat([model_CTL04E.params, pd.Series(corrected_pvals_CTL04E, index=model_CTL04E.params.index[1:])], axis = 1).iloc[1:]
df_results_CTL04E.columns = ['intercept', 'pvalue']
In [113]:
pvals = pd.concat([df_results_CTL08A['pvalue'], df_results_CTL04E['pvalue']], axis = 1)
pvals.columns = ['CTL08A', 'CTL04E']
pvals.index = [i.split('C(Condition, Treatment(reference=\'DMSO\'))[T.')[1].replace(']', '') for i in pvals.index]
pvals
Out[113]:
CTL08A CTL04E
ANDR_AG 0.026175 0.004128
ANDR_INH 0.000545 0.031130
ARYL_AG 0.005701 0.564293
ARYL_INH 0.000246 0.433723
ESTR_AG 0.000100 0.481559
ESTR_INH 0.584482 0.009591
GC_AG 0.584482 NaN
GC_INH 0.393980 0.031130
LX_AG 0.035222 0.199756
LX_INH 0.393980 0.365859
RET_AG 0.000100 NaN
RET_INH 0.584482 0.067244
THYR_AG 0.294518 0.564293
THYR_INH 0.409853 0.802613
In [114]:
intercepts = pd.concat([df_results_CTL08A['intercept'], df_results_CTL04E['intercept']], axis = 1)
intercepts.columns = ['CTL08A', 'CTL04E']
intercepts.index = [i.split('C(Condition, Treatment(reference=\'DMSO\'))[T.')[1].replace(']', '') for i in intercepts.index]

params = model_CTL08A.params
mean_DMSO = params["Intercept"]
        
intercepts['CTL08A'] = intercepts['CTL08A']/ mean_DMSO * 100

params = model_CTL04E.params
mean_DMSO = params["Intercept"]
intercepts['CTL04E'] = intercepts['CTL04E']/ mean_DMSO * 100
intercepts
Out[114]:
CTL08A CTL04E
ANDR_AG 4.291810 5.948758
ANDR_INH 8.096875 4.543271
ARYL_AG 4.826548 1.016616
ARYL_INH 7.128947 1.509976
ESTR_AG 8.644100 -1.536757
ESTR_INH 1.069257 -4.713942
GC_AG -1.009511 NaN
GC_INH 1.871787 -3.758465
LX_AG 4.004447 -2.456410
LX_INH 2.238430 -1.776391
RET_AG 7.746693 NaN
RET_INH -1.076113 -3.496482
THYR_AG 2.401156 0.919046
THYR_INH -1.719633 -0.475073
In [115]:
fig, ax = plt.subplots(figsize = (4,7))
intercepts = intercepts.applymap(lambda x: f"{x:+.1f}%" if pd.notnull(x) else "")
intercepts = intercepts[pvals < 0.05].fillna('')
sns.heatmap(pvals, annot=intercepts, fmt='', cmap='Greens_r', cbar_kws={'label': 'p-value'}, vmin=0, vmax=0.05, ax = ax)
plt.title('Condition vs DMSO\n')
plt.savefig('../../../../endpoints_figures/endpoints_organoids_eccentricity_OLS_test.png', dpi=300, bbox_inches='tight')
/tmp/ipykernel_1121090/3100479939.py:2: FutureWarning: DataFrame.applymap has been deprecated. Use DataFrame.map instead.
  intercepts = intercepts.applymap(lambda x: f"{x:+.1f}%" if pd.notnull(x) else "")

Plot of orientation¶

Main contributor of variance for PC3

In [116]:
fig, ax = plt.subplots(1, 2, figsize=(20, 5))

for i, cond in enumerate(hormonal_conditions):
    data = tot_df_morphological[(tot_df_morphological['Line'] == 'CTL08A') & 
                                 (tot_df_morphological['Condition'] == cond)]['orientation']
    box = ax[0].boxplot(data, positions=[i], patch_artist=True, widths=.8)
    for patch in box['boxes']:
        patch.set_facecolor(color_palette[cond])
        box['medians'][0].set_color('black')
    dmso_median = tot_df_morphological[(tot_df_morphological['Condition'] == 'DMSO') & 
                                       (tot_df_morphological['Line'] == 'CTL08A')]['orientation'].median()
    ax[0].axhline(dmso_median, color='red', linestyle='--', linewidth=.7, alpha=0.7)

for i, cond in enumerate(hormonal_conditions):
    data = tot_df_morphological[(tot_df_morphological['Line'] == 'CTL04E') & 
                                 (tot_df_morphological['Condition'] == cond)]['orientation']
    box = ax[1].boxplot(data, positions=[i], patch_artist=True, widths=.8)
    for patch in box['boxes']:
        patch.set_facecolor(color_palette[cond])
        box['medians'][0].set_color('black')
    dmso_median = tot_df_morphological[(tot_df_morphological['Condition'] == 'DMSO') & 
                                       (tot_df_morphological['Line'] == 'CTL04E')]['orientation'].median()
    ax[1].axhline(dmso_median, color='red', linestyle='--', linewidth=.7, alpha=0.7)

_ = ax[0].set_xticks(range(len(hormonal_conditions)))
_ = ax[0].set_xticklabels(hormonal_conditions, rotation=90)

_ = ax[1].set_xticks(range(len(hormonal_conditions)))
_ = ax[1].set_xticklabels(hormonal_conditions, rotation=90)

ax[0].set_title('CTL08A')
ax[1].set_title('CTL04E')

ax[0].set_ylabel('orientation')
ax[1].set_ylabel('orientation')

plt.savefig('../../../../endpoints_figures/endpoints_orientation_all_nuclei.pdf', dpi=300, bbox_inches='tight')
plt.savefig('../../../../endpoints_figures/endpoints_orientation_all_nuclei.png', dpi=300, bbox_inches='tight')

Grouped by mean per organoid¶

In [117]:
fig, ax = plt.subplots(1, 2, figsize=(20, 5))

for i, cond in enumerate(hormonal_conditions):
    data = grouped_df[(grouped_df['Line'] == 'CTL08A') & 
                                 (grouped_df['Condition'] == cond)]['orientation']
    box = ax[0].boxplot(data, positions=[i], patch_artist=True, widths=.8)
    for patch in box['boxes']:
        patch.set_facecolor(color_palette[cond])
        box['medians'][0].set_color('black')
        # Add stripplot for CTL08A (single points per box)

    dmso_median = grouped_df[(grouped_df['Condition'] == 'DMSO') & 
                                       (grouped_df['Line'] == 'CTL08A')]['orientation'].median()
    ax[0].axhline(dmso_median, color='red', linestyle='--', linewidth=.7, alpha=0.7)

    sns.stripplot(
        data=grouped_df[(grouped_df['Line'] == 'CTL08A') & (grouped_df['Condition'] == cond)],
        y='orientation', x='Condition', order=[cond], color='black', alpha=0.5, jitter=False, ax=ax[0], size=8
    )

for i, cond in enumerate(hormonal_conditions):
    data = grouped_df[(grouped_df['Line'] == 'CTL04E') & 
                                 (grouped_df['Condition'] == cond)]['orientation']
    box = ax[1].boxplot(data, positions=[i], patch_artist=True, widths=.8)
    for patch in box['boxes']:
        patch.set_facecolor(color_palette[cond])
        box['medians'][0].set_color('black')
    dmso_median = grouped_df[(grouped_df['Condition'] == 'DMSO') & 
                                       (grouped_df['Line'] == 'CTL04E')]['orientation'].median()
    ax[1].axhline(dmso_median, color='red', linestyle='--', linewidth=.7, alpha=0.7)

    sns.stripplot(
        data=grouped_df[(grouped_df['Line'] == 'CTL04E') & (grouped_df['Condition'] == cond)],
        y='orientation', x='Condition', order=[cond], color='black', alpha=0.5, jitter=False, ax=ax[1], size=8
    )

_ = ax[0].set_xticks(range(len(hormonal_conditions)))
_ = ax[0].set_xticklabels(hormonal_conditions, rotation=90)

_ = ax[1].set_xticks(range(len(hormonal_conditions)))
_ = ax[1].set_xticklabels(hormonal_conditions, rotation=90)

ax[0].set_title('CTL08A')
ax[1].set_title('CTL04E')

ax[0].set_ylabel('orientation')
ax[1].set_ylabel('orientation')

plt.savefig('../../../../endpoints_figures/endpoints_orientation_grouped.pdf', dpi=300, bbox_inches='tight')
plt.savefig('../../../../endpoints_figures/endpoints_orientation_grouped.png', dpi=300, bbox_inches='tight')

Plot of nuclei area¶

In [118]:
tot_df_morphological['area_um2'] = tot_df_morphological['area'] * (0.325 ** 2)  # Convert area to um^2
In [119]:
fig, ax = plt.subplots(1, 2, figsize=(20, 5))

for i, cond in enumerate(hormonal_conditions):
    data = tot_df_morphological[(tot_df_morphological['Line'] == 'CTL08A') & 
                                 (tot_df_morphological['Condition'] == cond)]['area_um2']
    box = ax[0].boxplot(data, positions=[i], patch_artist=True, widths=.8)
    for patch in box['boxes']:
        patch.set_facecolor(color_palette[cond])
        box['medians'][0].set_color('black')
    dmso_median = tot_df_morphological[(tot_df_morphological['Condition'] == 'DMSO') & 
                                       (tot_df_morphological['Line'] == 'CTL08A')]['area_um2'].median()
    ax[0].axhline(dmso_median, color='red', linestyle='--', linewidth=.7, alpha=0.7)

for i, cond in enumerate(hormonal_conditions):
    data = tot_df_morphological[(tot_df_morphological['Line'] == 'CTL04E') & 
                                 (tot_df_morphological['Condition'] == cond)]['area_um2']
    box = ax[1].boxplot(data, positions=[i], patch_artist=True, widths=.8)
    for patch in box['boxes']:
        patch.set_facecolor(color_palette[cond])
        box['medians'][0].set_color('black')
    dmso_median = tot_df_morphological[(tot_df_morphological['Condition'] == 'DMSO') & 
                                       (tot_df_morphological['Line'] == 'CTL04E')]['area_um2'].median()
    ax[1].axhline(dmso_median, color='red', linestyle='--', linewidth=.7, alpha=0.7)

_ = ax[0].set_xticks(range(len(hormonal_conditions)))
_ = ax[0].set_xticklabels(hormonal_conditions, rotation=90)

_ = ax[1].set_xticks(range(len(hormonal_conditions)))
_ = ax[1].set_xticklabels(hormonal_conditions, rotation=90)

ax[0].set_title('CTL08A')
ax[1].set_title('CTL04E')

ax[0].set_ylabel('area_um2')
ax[1].set_ylabel('area_um2')

plt.savefig('../../../../endpoints_figures/endpoints_area_um2_all_nuclei.pdf', dpi=300, bbox_inches='tight')
plt.savefig('../../../../endpoints_figures/endpoints_area_um2_all_nuclei.png', dpi=300, bbox_inches='tight')

Grouped by mean per organoid¶

In [120]:
grouped_df['area_um2'] = grouped_df['area'] * (0.325 ** 2)  # Convert area to um^2
In [121]:
fig, ax = plt.subplots(1, 2, figsize=(20, 5))

for i, cond in enumerate(hormonal_conditions):
    data = grouped_df[(grouped_df['Line'] == 'CTL08A') & 
                                 (grouped_df['Condition'] == cond)]['area_um2']
    box = ax[0].boxplot(data, positions=[i], patch_artist=True, widths=.8)
    for patch in box['boxes']:
        patch.set_facecolor(color_palette[cond])
        box['medians'][0].set_color('black')
        # Add stripplot for CTL08A (single points per box)

    dmso_median = grouped_df[(grouped_df['Condition'] == 'DMSO') & 
                                       (grouped_df['Line'] == 'CTL08A')]['area_um2'].median()
    ax[0].axhline(dmso_median, color='red', linestyle='--', linewidth=.7, alpha=0.7)

sns.stripplot(
    data=grouped_df[(grouped_df['Line'] == 'CTL08A')],
    y='area_um2', x='Condition', order=hormonal_conditions, color='black', alpha=0.5, jitter=False, ax=ax[0], size=8
)

for i, cond in enumerate(hormonal_conditions):
    data = grouped_df[(grouped_df['Line'] == 'CTL04E') & 
                                 (grouped_df['Condition'] == cond)]['area_um2']
    box = ax[1].boxplot(data, positions=[i], patch_artist=True, widths=.8)
    for patch in box['boxes']:
        patch.set_facecolor(color_palette[cond])
        box['medians'][0].set_color('black')
    dmso_median = grouped_df[(grouped_df['Condition'] == 'DMSO') & 
                                       (grouped_df['Line'] == 'CTL04E')]['area_um2'].median()
    ax[1].axhline(dmso_median, color='red', linestyle='--', linewidth=.7, alpha=0.7)

sns.stripplot(
    data=grouped_df[(grouped_df['Line'] == 'CTL04E')],
    y='area_um2', x='Condition', order=hormonal_conditions, color='black', alpha=0.5, jitter=False, ax=ax[1], size=8
)

_ = ax[0].set_xticks(range(len(hormonal_conditions)))
_ = ax[0].set_xticklabels(hormonal_conditions, rotation=90)

_ = ax[1].set_xticks(range(len(hormonal_conditions)))
_ = ax[1].set_xticklabels(hormonal_conditions, rotation=90)

ax[0].set_title('CTL08A')
ax[1].set_title('CTL04E')

ax[0].set_ylabel('Nuclei area (um2)')
ax[1].set_ylabel('Nuclei area (um2)')

plt.savefig('../../../../endpoints_figures/endpoints_area_um2_grouped.pdf', dpi=300, bbox_inches='tight')
plt.savefig('../../../../endpoints_figures/endpoints_area_um2_grouped.png', dpi=300, bbox_inches='tight')

Statistical test - Area¶

In [122]:
grouped_df_CTL08A = grouped_df[grouped_df['Line'] == 'CTL08A']
grouped_df_CTL08A['Condition'] = grouped_df_CTL08A['Condition'].astype('category')
grouped_df_CTL08A["log_area"] = np.log(grouped_df_CTL08A["area_um2"])
grouped_df_CTL08A['log_area'].hist(bins = 10)

model_CTL08A = smf.ols("log_area ~ C(Condition, Treatment(reference='DMSO'))",
                data=grouped_df_CTL08A,
                ).fit()

print(model_CTL08A.summary())
                            OLS Regression Results                            
==============================================================================
Dep. Variable:               log_area   R-squared:                       0.590
Model:                            OLS   Adj. R-squared:                  0.499
Method:                 Least Squares   F-statistic:                     6.484
Date:                Tue, 28 Apr 2026   Prob (F-statistic):           7.59e-08
Time:                        15:56:45   Log-Likelihood:                 19.126
No. Observations:                  78   AIC:                            -8.251
Df Residuals:                      63   BIC:                             27.10
Df Model:                          14                                         
Covariance Type:            nonrobust                                         
=========================================================================================================================
                                                            coef    std err          t      P>|t|      [0.025      0.975]
-------------------------------------------------------------------------------------------------------------------------
Intercept                                                 3.4752      0.086     40.402      0.000       3.303       3.647
C(Condition, Treatment(reference='DMSO'))[T.ANDR_AG]      0.1418      0.122      1.165      0.248      -0.101       0.385
C(Condition, Treatment(reference='DMSO'))[T.ANDR_INH]     0.4025      0.149      2.701      0.009       0.105       0.700
C(Condition, Treatment(reference='DMSO'))[T.ARYL_AG]      0.2294      0.111      2.065      0.043       0.007       0.451
C(Condition, Treatment(reference='DMSO'))[T.ARYL_INH]     0.2732      0.122      2.246      0.028       0.030       0.516
C(Condition, Treatment(reference='DMSO'))[T.ESTR_AG]      0.2892      0.136      2.127      0.037       0.017       0.561
C(Condition, Treatment(reference='DMSO'))[T.ESTR_INH]     0.2084      0.136      1.532      0.130      -0.063       0.480
C(Condition, Treatment(reference='DMSO'))[T.GC_AG]        0.1330      0.136      0.978      0.332      -0.139       0.405
C(Condition, Treatment(reference='DMSO'))[T.GC_INH]       0.2446      0.128      1.917      0.060      -0.010       0.500
C(Condition, Treatment(reference='DMSO'))[T.LX_AG]        0.2182      0.122      1.794      0.078      -0.025       0.461
C(Condition, Treatment(reference='DMSO'))[T.LX_INH]       0.0831      0.149      0.558      0.579      -0.215       0.381
C(Condition, Treatment(reference='DMSO'))[T.RET_AG]       0.9205      0.122      7.567      0.000       0.677       1.164
C(Condition, Treatment(reference='DMSO'))[T.RET_INH]      0.0280      0.122      0.230      0.819      -0.215       0.271
C(Condition, Treatment(reference='DMSO'))[T.THYR_AG]      0.0510      0.128      0.400      0.690      -0.204       0.306
C(Condition, Treatment(reference='DMSO'))[T.THYR_INH]     0.0367      0.128      0.288      0.774      -0.218       0.292
==============================================================================
Omnibus:                       29.224   Durbin-Watson:                   1.338
Prob(Omnibus):                  0.000   Jarque-Bera (JB):              292.528
Skew:                           0.536   Prob(JB):                     3.01e-64
Kurtosis:                      12.427   Cond. No.                         15.0
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
/tmp/ipykernel_1121090/2562141296.py:2: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  grouped_df_CTL08A['Condition'] = grouped_df_CTL08A['Condition'].astype('category')
/tmp/ipykernel_1121090/2562141296.py:3: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  grouped_df_CTL08A["log_area"] = np.log(grouped_df_CTL08A["area_um2"])
In [123]:
grouped_df_CTL04E = grouped_df[grouped_df['Line'] == 'CTL04E']
grouped_df_CTL04E['Condition'] = grouped_df_CTL04E['Condition'].astype('category')
grouped_df_CTL04E["log_area"] = np.log(grouped_df_CTL04E["area_um2"])
grouped_df_CTL04E['log_area'].hist(bins = 10)

model_CTL04E = smf.ols("log_area ~ C(Condition, Treatment(reference='DMSO'))",
                data=grouped_df_CTL04E,
                ).fit()

print(model_CTL04E.summary())
                            OLS Regression Results                            
==============================================================================
Dep. Variable:               log_area   R-squared:                       0.709
Model:                            OLS   Adj. R-squared:                  0.636
Method:                 Least Squares   F-statistic:                     9.730
Date:                Tue, 28 Apr 2026   Prob (F-statistic):           3.22e-09
Time:                        15:56:45   Log-Likelihood:                 67.975
No. Observations:                  61   AIC:                            -110.0
Df Residuals:                      48   BIC:                            -82.51
Df Model:                          12                                         
Covariance Type:            nonrobust                                         
=========================================================================================================================
                                                            coef    std err          t      P>|t|      [0.025      0.975]
-------------------------------------------------------------------------------------------------------------------------
Intercept                                                 3.5808      0.045     80.013      0.000       3.491       3.671
C(Condition, Treatment(reference='DMSO'))[T.ANDR_AG]      0.1083      0.063      1.712      0.093      -0.019       0.236
C(Condition, Treatment(reference='DMSO'))[T.ANDR_INH]     0.1091      0.068      1.596      0.117      -0.028       0.247
C(Condition, Treatment(reference='DMSO'))[T.ARYL_AG]     -0.1309      0.058     -2.266      0.028      -0.247      -0.015
C(Condition, Treatment(reference='DMSO'))[T.ARYL_INH]     0.1562      0.058      2.703      0.009       0.040       0.272
C(Condition, Treatment(reference='DMSO'))[T.ESTR_AG]      0.0240      0.068      0.350      0.728      -0.113       0.161
C(Condition, Treatment(reference='DMSO'))[T.ESTR_INH]    -0.0737      0.058     -1.275      0.208      -0.190       0.042
C(Condition, Treatment(reference='DMSO'))[T.GC_INH]      -0.1863      0.058     -3.224      0.002      -0.302      -0.070
C(Condition, Treatment(reference='DMSO'))[T.LX_AG]       -0.1903      0.060     -3.169      0.003      -0.311      -0.070
C(Condition, Treatment(reference='DMSO'))[T.LX_INH]      -0.1523      0.058     -2.636      0.011      -0.268      -0.036
C(Condition, Treatment(reference='DMSO'))[T.RET_INH]     -0.1244      0.063     -1.966      0.055      -0.252       0.003
C(Condition, Treatment(reference='DMSO'))[T.THYR_AG]     -0.0581      0.058     -1.006      0.320      -0.174       0.058
C(Condition, Treatment(reference='DMSO'))[T.THYR_INH]     0.2152      0.078      2.776      0.008       0.059       0.371
==============================================================================
Omnibus:                        5.053   Durbin-Watson:                   2.590
Prob(Omnibus):                  0.080   Jarque-Bera (JB):                3.099
Skew:                          -0.359   Prob(JB):                        0.212
Kurtosis:                       2.161   Cond. No.                         15.1
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
/tmp/ipykernel_1121090/4157921110.py:2: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  grouped_df_CTL04E['Condition'] = grouped_df_CTL04E['Condition'].astype('category')
/tmp/ipykernel_1121090/4157921110.py:3: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  grouped_df_CTL04E["log_area"] = np.log(grouped_df_CTL04E["area_um2"])
In [124]:
resid_CTL08A = model_CTL08A.resid

stats.probplot(resid_CTL08A, dist="norm", plot=plt)
plt.show()
In [125]:
resid_CTL04E = model_CTL04E.resid

stats.probplot(resid_CTL04E, dist="norm", plot=plt)
plt.show()
In [126]:
pvals_CTL08A = model_CTL08A.pvalues[1:]  # exclude intercept
corrected_pvals_CTL08A = multipletests(pvals_CTL08A, method='fdr_bh')[1]

df_results_CTL08A = pd.concat([model_CTL08A.params, pd.Series(corrected_pvals_CTL08A, index=model_CTL08A.params.index[1:])], axis = 1).iloc[1:]
df_results_CTL08A.columns = ['intercept', 'pvalue']
In [127]:
pvals_CTL04E = model_CTL04E.pvalues[1:]  # exclude intercept
corrected_pvals_CTL04E = multipletests(pvals_CTL04E, method='fdr_bh')[1]

df_results_CTL04E = pd.concat([model_CTL04E.params, pd.Series(corrected_pvals_CTL04E, index=model_CTL04E.params.index[1:])], axis = 1).iloc[1:]
df_results_CTL04E.columns = ['intercept', 'pvalue']
In [128]:
pvals = pd.concat([df_results_CTL08A['pvalue'], df_results_CTL04E['pvalue']], axis = 1)
pvals.columns = ['CTL08A', 'CTL04E']
pvals.index = [i.split('C(Condition, Treatment(reference=\'DMSO\'))[T.')[1].replace(']', '') for i in pvals.index]
pvals
Out[128]:
CTL08A CTL04E
ANDR_AG 3.861730e-01 0.140144
ANDR_INH 6.201731e-02 0.155963
ARYL_AG 1.204013e-01 0.055995
ARYL_INH 1.204013e-01 0.027070
ESTR_AG 1.204013e-01 0.727503
ESTR_INH 2.282874e-01 0.250067
GC_AG 4.645990e-01 NaN
GC_INH 1.393849e-01 0.015985
LX_AG 1.553794e-01 0.015985
LX_INH 7.368958e-01 0.027070
RET_AG 2.860717e-09 NaN
RET_INH 8.188953e-01 0.094554
THYR_AG 8.055180e-01 0.348721
THYR_INH 8.188953e-01 0.027070
In [129]:
intercepts = pd.concat([df_results_CTL08A['intercept'], df_results_CTL04E['intercept']], axis = 1)
intercepts.columns = ['CTL08A', 'CTL04E']
intercepts.index = [i.split('C(Condition, Treatment(reference=\'DMSO\'))[T.')[1].replace(']', '') for i in intercepts.index]
intercepts = (np.exp(intercepts) - 1) * 100
intercepts
Out[129]:
CTL08A CTL04E
ANDR_AG 15.230298 11.441127
ANDR_INH 49.550300 11.530624
ARYL_AG 25.779160 -12.271330
ARYL_INH 31.417438 16.903754
ESTR_AG 33.541879 2.424938
ESTR_INH 23.170375 -7.102512
GC_AG 14.224804 NaN
GC_INH 27.712485 -16.995242
LX_AG 24.379871 -17.325295
LX_INH 8.664554 -14.124428
RET_AG 151.044298 NaN
RET_INH 2.836367 -11.697077
THYR_AG 5.236901 -5.644258
THYR_INH 3.741964 24.006274
In [130]:
fig, ax = plt.subplots(figsize = (4,7))
intercepts = intercepts.applymap(lambda x: f"{x:+.1f}%" if pd.notnull(x) else "")
intercepts = intercepts[pvals < 0.05].fillna('')
sns.heatmap(pvals, annot=intercepts, fmt='', cmap='Greens_r', cbar_kws={'label': 'p-value'}, vmin=0, vmax=0.05, ax = ax)
plt.title('Condition vs DMSO\n')
plt.savefig('../../../../endpoints_figures/endpoints_organoids_nuclei_area_OLS_test.png', dpi=300, bbox_inches='tight')
/tmp/ipykernel_1121090/2973917183.py:2: FutureWarning: DataFrame.applymap has been deprecated. Use DataFrame.map instead.
  intercepts = intercepts.applymap(lambda x: f"{x:+.1f}%" if pd.notnull(x) else "")

Organoid areas¶

In [131]:
organoid_areas = pd.read_csv('/group/testa/Project/EndPoints/TPSSU/analysis/organoids_area.csv', index_col=0)
organoid_areas
Out[131]:
area
20240424_manuel_lessi_0141_ScanRegion1_rep_0 48618374
20240424_manuel_lessi_0131_ScanRegion0_rep_2 36008220
20240424_manuel_lessi_0133_ScanRegion4_rep_0 8422522
20240424_manuel_lessi_0143_ScanRegion2_rep_1 61481135
20240424_manuel_lessi_0147_ScanRegion0_rep_1 162545379
... ...
20240424_manuel_lessi_0129_ScanRegion1_rep_0 26572184
20240424_manuel_lessi_0129_ScanRegion0_rep_0 9292198
20240424_manuel_lessi_0142_ScanRegion1_rep_2 38017446
20240424_manuel_lessi_0129_ScanRegion2_rep_1 29459387
20240424_manuel_lessi_0134_ScanRegion4_rep_1 22517768

280 rows × 1 columns

In [132]:
organoid_areas['ImageName_ScanRegion'] = organoid_areas.reset_index()['index'].apply(lambda x: '_'.join(x.split('_')[0:5])).values
organoid_areas['Condition_Line'] = organoid_areas['ImageName_ScanRegion'].map(sample_dict)
organoid_areas['Line'] = organoid_areas['Condition_Line'].apply(lambda x: x.split('_')[-1] if pd.notnull(x) else x)
organoid_areas['Condition'] = organoid_areas['Condition_Line'].str.replace('_CTL08A', '', regex=True)
organoid_areas['Condition'] = organoid_areas['Condition'].str.replace('_CTL04E', '', regex=True)
organoid_areas = organoid_areas[organoid_areas['Condition'].isin(hormonal_conditions)]
organoid_areas
Out[132]:
area ImageName_ScanRegion Condition_Line Line Condition
20240424_manuel_lessi_0131_ScanRegion0_rep_2 36008220 20240424_manuel_lessi_0131_ScanRegion0 GC_INH_CTL04E CTL04E GC_INH
20240424_manuel_lessi_0133_ScanRegion4_rep_0 8422522 20240424_manuel_lessi_0133_ScanRegion4 RET_AG_CTL08A CTL08A RET_AG
20240424_manuel_lessi_0140_ScanRegion4_rep_2 32680044 20240424_manuel_lessi_0140_ScanRegion4 THYR_INH_CTL08A CTL08A THYR_INH
20240424_manuel_lessi_0140_ScanRegion0_rep_1 36008064 20240424_manuel_lessi_0140_ScanRegion0 GC_INH_CTL08A CTL08A GC_INH
20240424_manuel_lessi_0138_ScanRegion3_rep_1 16957648 20240424_manuel_lessi_0138_ScanRegion3 ARYL_AG_CTL08A CTL08A ARYL_AG
... ... ... ... ... ...
20240424_manuel_lessi_0131_ScanRegion4_rep_1 35725975 20240424_manuel_lessi_0131_ScanRegion4 THYR_AG_CTL04E CTL04E THYR_AG
20240424_manuel_lessi_0140_ScanRegion3_rep_0 46296832 20240424_manuel_lessi_0140_ScanRegion3 THYR_AG_CTL04E CTL04E THYR_AG
20240424_manuel_lessi_0128_ScanRegion4_rep_0 42198648 20240424_manuel_lessi_0128_ScanRegion4 ESTR_INH_CTL08A CTL08A ESTR_INH
20240424_manuel_lessi_0129_ScanRegion1_rep_0 26572184 20240424_manuel_lessi_0129_ScanRegion1 ARYL_INH_CTL08A CTL08A ARYL_INH
20240424_manuel_lessi_0129_ScanRegion2_rep_1 29459387 20240424_manuel_lessi_0129_ScanRegion2 ARYL_INH_CTL04E CTL04E ARYL_INH

146 rows × 5 columns

In [133]:
organoid_areas['area_um2'] = organoid_areas['area'] * (0.325 ** 2)  # Convert area to um^2
/tmp/ipykernel_1121090/172599718.py:1: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  organoid_areas['area_um2'] = organoid_areas['area'] * (0.325 ** 2)  # Convert area to um^2
In [134]:
summed_area = tot_df_morphological[['ImageName_ScanRegion_Rep', 'area_um2']].groupby('ImageName_ScanRegion_Rep').sum()
In [135]:
organoid_areas['tot_nuclei_areas'] = summed_area
/tmp/ipykernel_1121090/1334222756.py:1: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  organoid_areas['tot_nuclei_areas'] = summed_area
In [136]:
organoid_areas['nuclear_area'] = organoid_areas['tot_nuclei_areas'] / organoid_areas['area_um2'] * 100
/tmp/ipykernel_1121090/3125954352.py:1: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  organoid_areas['nuclear_area'] = organoid_areas['tot_nuclei_areas'] / organoid_areas['area_um2'] * 100
In [137]:
organoid_areas
Out[137]:
area ImageName_ScanRegion Condition_Line Line Condition area_um2 tot_nuclei_areas nuclear_area
20240424_manuel_lessi_0131_ScanRegion0_rep_2 36008220 20240424_manuel_lessi_0131_ScanRegion0 GC_INH_CTL04E CTL04E GC_INH 3.803368e+06 1.145734e+06 30.124186
20240424_manuel_lessi_0133_ScanRegion4_rep_0 8422522 20240424_manuel_lessi_0133_ScanRegion4 RET_AG_CTL08A CTL08A RET_AG 8.896289e+05 2.439548e+05 27.422083
20240424_manuel_lessi_0140_ScanRegion4_rep_2 32680044 20240424_manuel_lessi_0140_ScanRegion4 THYR_INH_CTL08A CTL08A THYR_INH 3.451830e+06 1.302871e+06 37.744374
20240424_manuel_lessi_0140_ScanRegion0_rep_1 36008064 20240424_manuel_lessi_0140_ScanRegion0 GC_INH_CTL08A CTL08A GC_INH 3.803352e+06 1.457129e+06 38.311710
20240424_manuel_lessi_0138_ScanRegion3_rep_1 16957648 20240424_manuel_lessi_0138_ScanRegion3 ARYL_AG_CTL08A CTL08A ARYL_AG 1.791152e+06 6.871573e+05 38.363994
... ... ... ... ... ... ... ... ...
20240424_manuel_lessi_0131_ScanRegion4_rep_1 35725975 20240424_manuel_lessi_0131_ScanRegion4 THYR_AG_CTL04E CTL04E THYR_AG 3.773556e+06 1.162948e+06 30.818358
20240424_manuel_lessi_0140_ScanRegion3_rep_0 46296832 20240424_manuel_lessi_0140_ScanRegion3 THYR_AG_CTL04E CTL04E THYR_AG 4.890103e+06 1.751093e+06 35.808912
20240424_manuel_lessi_0128_ScanRegion4_rep_0 42198648 20240424_manuel_lessi_0128_ScanRegion4 ESTR_INH_CTL08A CTL08A ESTR_INH 4.457232e+06 1.589967e+06 35.671624
20240424_manuel_lessi_0129_ScanRegion1_rep_0 26572184 20240424_manuel_lessi_0129_ScanRegion1 ARYL_INH_CTL08A CTL08A ARYL_INH 2.806687e+06 1.140692e+06 40.641951
20240424_manuel_lessi_0129_ScanRegion2_rep_1 29459387 20240424_manuel_lessi_0129_ScanRegion2 ARYL_INH_CTL04E CTL04E ARYL_INH 3.111648e+06 1.188860e+06 38.206776

146 rows × 8 columns

In [138]:
fig, ax = plt.subplots(1, 2, figsize=(20, 5))

for i, cond in enumerate(hormonal_conditions):
    data = organoid_areas[(organoid_areas['Line'] == 'CTL08A') & 
                                 (organoid_areas['Condition'] == cond)]['nuclear_area']
    box = ax[0].boxplot(data, positions=[i], patch_artist=True, widths=.8)
    for patch in box['boxes']:
        patch.set_facecolor(color_palette[cond])
        box['medians'][0].set_color('black')
        # Add stripplot for CTL08A (single points per box)

    dmso_median = organoid_areas[(organoid_areas['Condition'] == 'DMSO') & 
                                       (organoid_areas['Line'] == 'CTL08A')]['nuclear_area'].median()
    ax[0].axhline(dmso_median, color='red', linestyle='--', linewidth=.7, alpha=0.7)

sns.stripplot(
    data=organoid_areas[(organoid_areas['Line'] == 'CTL08A')],
    y='nuclear_area', x='Condition', order=hormonal_conditions, color='black', alpha=0.5, jitter=False, ax=ax[0], size=8
)

for i, cond in enumerate(hormonal_conditions):
    data = organoid_areas[(organoid_areas['Line'] == 'CTL04E') & 
                                 (organoid_areas['Condition'] == cond)]['nuclear_area']
    box = ax[1].boxplot(data, positions=[i], patch_artist=True, widths=.8)
    for patch in box['boxes']:
        patch.set_facecolor(color_palette[cond])
        box['medians'][0].set_color('black')
    dmso_median = organoid_areas[(organoid_areas['Condition'] == 'DMSO') & 
                                       (organoid_areas['Line'] == 'CTL04E')]['nuclear_area'].median()
    ax[1].axhline(dmso_median, color='red', linestyle='--', linewidth=.7, alpha=0.7)

sns.stripplot(
    data=organoid_areas[(organoid_areas['Line'] == 'CTL04E')],
    y='nuclear_area', x='Condition', order=hormonal_conditions, color='black', alpha=0.5, jitter=False, ax=ax[1], size=8
)

_ = ax[0].set_xticks(range(len(hormonal_conditions)))
_ = ax[0].set_xticklabels(hormonal_conditions, rotation=90)

_ = ax[1].set_xticks(range(len(hormonal_conditions)))
_ = ax[1].set_xticklabels(hormonal_conditions, rotation=90)

ax[0].set_title('CTL08A')
ax[1].set_title('CTL04E')

ax[0].set_ylabel('nuclear_area')
ax[1].set_ylabel('nuclear_area')
Out[138]:
Text(0, 0.5, 'nuclear_area')
In [139]:
measurements_files = glob.glob("/group/testa/Project/EndPoints/TPSSU/analysis/measurements/*.csv")
measurements_files_mix_1 = [f for f in measurements_files if any(name in f for name in mix_1_names)]
In [140]:
tot_df_mix1_ch2 = pd.DataFrame()
measurements_files_mix_1_ch2 = [f for f in measurements_files_mix_1 if 'channel_2' in f]

for file in measurements_files_mix_1_ch2:
    print(file)
    df = pd.read_csv(file, index_col=0)
    df['ImageName'] = file.split("/")[-1].split(".")[0]
    print(df.shape)
    tot_df_mix1_ch2 = pd.concat([tot_df_mix1_ch2, df], axis=0)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0129_ScanRegion3_rep_1_channel_2_measures.csv
(14032, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0134_ScanRegion2_rep_0_channel_2_measures.csv
(36257, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0134_ScanRegion3_rep_1_channel_2_measures.csv
(29165, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0133_ScanRegion2_rep_0_channel_2_measures.csv
(28234, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0129_ScanRegion1_rep_2_channel_2_measures.csv
(5186, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0134_ScanRegion2_rep_1_channel_2_measures.csv
(30598, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0129_ScanRegion0_rep_0_channel_2_measures.csv
(4420, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0133_ScanRegion4_rep_0_channel_2_measures.csv
(5637, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0128_ScanRegion5_rep_2_channel_2_measures.csv
(44878, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0128_ScanRegion2_rep_1_channel_2_measures.csv
(25453, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0134_ScanRegion5_rep_0_channel_2_measures.csv
(14547, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0132_ScanRegion2_rep_0_channel_2_measures.csv
(65200, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0134_ScanRegion4_rep_1_channel_2_measures.csv
(18423, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0129_ScanRegion0_rep_2_channel_2_measures.csv
(12038, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0131_ScanRegion0_rep_1_channel_2_measures.csv
(52451, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0133_ScanRegion0_rep_0_channel_2_measures.csv
(69681, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0130_ScanRegion3_rep_0_channel_2_measures.csv
(60277, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0134_ScanRegion7_rep_0_channel_2_measures.csv
(36423, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0130_ScanRegion2_rep_1_channel_2_measures.csv
(54724, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0132_ScanRegion3_rep_2_channel_2_measures.csv
(48396, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0128_ScanRegion1_rep_0_channel_2_measures.csv
(18479, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0132_ScanRegion5_rep_0_channel_2_measures.csv
(61473, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0129_ScanRegion3_rep_2_channel_2_measures.csv
(62393, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0134_ScanRegion5_rep_2_channel_2_measures.csv
(27998, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0134_ScanRegion4_rep_0_channel_2_measures.csv
(16418, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0134_ScanRegion0_rep_0_channel_2_measures.csv
(54097, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0130_ScanRegion0_rep_1_channel_2_measures.csv
(19128, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0130_ScanRegion2_rep_2_channel_2_measures.csv
(7446, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0132_ScanRegion0_rep_0_channel_2_measures.csv
(6943, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0130_ScanRegion3_rep_2_channel_2_measures.csv
(44660, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0130_ScanRegion2_rep_0_channel_2_measures.csv
(6134, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0131_ScanRegion2_rep_0_channel_2_measures.csv
(43655, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0129_ScanRegion5_rep_0_channel_2_measures.csv
(55824, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0131_ScanRegion4_rep_0_channel_2_measures.csv
(43161, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0134_ScanRegion4_rep_2_channel_2_measures.csv
(26186, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0132_ScanRegion5_rep_2_channel_2_measures.csv
(42766, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0129_ScanRegion1_rep_0_channel_2_measures.csv
(32479, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0133_ScanRegion0_rep_2_channel_2_measures.csv
(46684, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0129_ScanRegion1_rep_1_channel_2_measures.csv
(7889, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0134_ScanRegion7_rep_1_channel_2_measures.csv
(1, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0133_ScanRegion3_rep_1_channel_2_measures.csv
(4878, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0134_ScanRegion5_rep_1_channel_2_measures.csv
(24682, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0128_ScanRegion3_rep_1_channel_2_measures.csv
(31993, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0134_ScanRegion3_rep_2_channel_2_measures.csv
(48939, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0129_ScanRegion4_rep_2_channel_2_measures.csv
(1405, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0134_ScanRegion0_rep_2_channel_2_measures.csv
(50003, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0130_ScanRegion4_rep_1_channel_2_measures.csv
(26358, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0128_ScanRegion1_rep_1_channel_2_measures.csv
(4685, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0133_ScanRegion0_rep_1_channel_2_measures.csv
(42864, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0129_ScanRegion4_rep_1_channel_2_measures.csv
(12551, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0132_ScanRegion1_rep_0_channel_2_measures.csv
(128250, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0128_ScanRegion2_rep_2_channel_2_measures.csv
(68825, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0129_ScanRegion0_rep_1_channel_2_measures.csv
(10615, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0134_ScanRegion2_rep_2_channel_2_measures.csv
(54944, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0129_ScanRegion2_rep_1_channel_2_measures.csv
(32605, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0129_ScanRegion2_rep_0_channel_2_measures.csv
(23699, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0131_ScanRegion3_rep_1_channel_2_measures.csv
(20919, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0131_ScanRegion4_rep_2_channel_2_measures.csv
(33055, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0132_ScanRegion3_rep_0_channel_2_measures.csv
(70503, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0134_ScanRegion1_rep_0_channel_2_measures.csv
(12349, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0130_ScanRegion1_rep_1_channel_2_measures.csv
(30782, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0134_ScanRegion6_rep_0_channel_2_measures.csv
(22787, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0128_ScanRegion4_rep_0_channel_2_measures.csv
(42538, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0128_ScanRegion5_rep_1_channel_2_measures.csv
(31370, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0129_ScanRegion3_rep_0_channel_2_measures.csv
(4465, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0134_ScanRegion0_rep_1_channel_2_measures.csv
(24746, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0133_ScanRegion1_rep_1_channel_2_measures.csv
(5061, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0131_ScanRegion5_rep_2_channel_2_measures.csv
(45127, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0134_ScanRegion1_rep_1_channel_2_measures.csv
(12207, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0128_ScanRegion5_rep_0_channel_2_measures.csv
(36668, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0130_ScanRegion4_rep_0_channel_2_measures.csv
(22478, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0131_ScanRegion0_rep_0_channel_2_measures.csv
(75915, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0129_ScanRegion4_rep_0_channel_2_measures.csv
(881, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0133_ScanRegion1_rep_2_channel_2_measures.csv
(25173, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0128_ScanRegion0_rep_1_channel_2_measures.csv
(9836, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0134_ScanRegion7_rep_2_channel_2_measures.csv
(27770, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0131_ScanRegion5_rep_0_channel_2_measures.csv
(52956, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0131_ScanRegion2_rep_2_channel_2_measures.csv
(55563, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0132_ScanRegion1_rep_1_channel_2_measures.csv
(92340, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0131_ScanRegion2_rep_1_channel_2_measures.csv
(2185, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0129_ScanRegion2_rep_2_channel_2_measures.csv
(13307, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0133_ScanRegion3_rep_0_channel_2_measures.csv
(47582, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0130_ScanRegion0_rep_2_channel_2_measures.csv
(1122, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0131_ScanRegion1_rep_0_channel_2_measures.csv
(5646, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0134_ScanRegion6_rep_1_channel_2_measures.csv
(22272, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0131_ScanRegion5_rep_1_channel_2_measures.csv
(59825, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0128_ScanRegion3_rep_0_channel_2_measures.csv
(11258, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0133_ScanRegion4_rep_2_channel_2_measures.csv
(6712, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0130_ScanRegion1_rep_0_channel_2_measures.csv
(26007, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0134_ScanRegion7_rep_3_channel_2_measures.csv
(72679, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0133_ScanRegion2_rep_1_channel_2_measures.csv
(17407, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0134_ScanRegion6_rep_2_channel_2_measures.csv
(4246, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0129_ScanRegion5_rep_1_channel_2_measures.csv
(79266, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0132_ScanRegion4_rep_1_channel_2_measures.csv
(10935, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0128_ScanRegion2_rep_0_channel_2_measures.csv
(24890, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0132_ScanRegion0_rep_1_channel_2_measures.csv
(24601, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0129_ScanRegion5_rep_2_channel_2_measures.csv
(58504, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0133_ScanRegion2_rep_2_channel_2_measures.csv
(7832, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0132_ScanRegion2_rep_1_channel_2_measures.csv
(194410, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0133_ScanRegion1_rep_0_channel_2_measures.csv
(2649, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0131_ScanRegion0_rep_2_channel_2_measures.csv
(35739, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0132_ScanRegion5_rep_1_channel_2_measures.csv
(72165, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0132_ScanRegion4_rep_0_channel_2_measures.csv
(79123, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0128_ScanRegion4_rep_1_channel_2_measures.csv
(17120, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0131_ScanRegion3_rep_0_channel_2_measures.csv
(43389, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0131_ScanRegion3_rep_2_channel_2_measures.csv
(19504, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0131_ScanRegion4_rep_1_channel_2_measures.csv
(38346, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0128_ScanRegion0_rep_0_channel_2_measures.csv
(70479, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0134_ScanRegion3_rep_0_channel_2_measures.csv
(50144, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0133_ScanRegion3_rep_2_channel_2_measures.csv
(1195, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0130_ScanRegion3_rep_1_channel_2_measures.csv
(54110, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0133_ScanRegion4_rep_1_channel_2_measures.csv
(4832, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0132_ScanRegion3_rep_1_channel_2_measures.csv
(39112, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0130_ScanRegion0_rep_0_channel_2_measures.csv
(41176, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0129_ScanRegion3_rep_3_channel_2_measures.csv
(9818, 19)
/group/testa/Project/EndPoints/TPSSU/analysis/measurements/20240424_manuel_lessi_0129_ScanRegion3_rep_4_channel_2_measures.csv
(23465, 19)
In [141]:
tot_pos_SOX2 = {}
density_SOX2 = {}
n_nuclei = {}
n_nuclei_tot_pos_SOX2 = {}
tot_area_SOX2 = {}
tot_area_no_SOX2 = {}

for file in measurements_files_mix_1_ch2:
    #print(file)
    df = pd.read_csv(file, index_col=0)
    
    imageName_scanRegion_rep = file.split('/')[-1].split('.')[0].replace('_channel_2_measures', '')
    imageName_scanRegion = '_'.join(imageName_scanRegion_rep.split('_')[:5])
    
    if imageName_scanRegion_rep in good_tissue_to_keep_limit and imageName_scanRegion in sample_sheet['ImageName_ScanRegion'].values:
        
        df['ImageName'] = file.split("/")[-1].split(".")[0]
        tot_pos_SOX2[file.split("/")[-1].split(".")[0]] = {}
        density_SOX2[imageName_scanRegion_rep] = {}
        n_nuclei_tot_pos_SOX2[imageName_scanRegion_rep] = {}

        df['area_um2'] = df['area'] * (0.325 ** 2)  # Convert area to um^2

        min_quantile = np.quantile(df['area_um2'], 0.01)

        max_quantile = np.quantile(df['area_um2'], 0.99)

        df = df[(df['area_um2'] > min_quantile) & (df['area_um2'] < max_quantile)]

        n_nuclei[file.split("/")[-1].split(".")[0]] = df.shape[0]

        otsu_thrs = filters.threshold_multiotsu(np.array(df['intensity_mean']), classes = 4)

        for i, thr in enumerate(otsu_thrs):
            tot_pos_SOX2[file.split("/")[-1].split(".")[0]][f'otsu_{i}'] = np.sum(df['intensity_mean'] > thr) / df.shape[0] * 100
            n_nuclei_tot_pos_SOX2[imageName_scanRegion_rep][f'otsu_{i}'] = np.sum(df['intensity_mean'] > thr)

            if i == 1:
                tot_area_SOX2[imageName_scanRegion_rep] = df[df['intensity_mean'] > thr]['area_um2'].sum()
                tot_area_no_SOX2[imageName_scanRegion_rep] = df[df['intensity_mean'] <= thr]['area_um2'].sum()
In [142]:
organoid_areas['SOX2_pos_area'] = organoid_areas.index.map(tot_area_SOX2)
organoid_areas['SOX2_neg_area'] = organoid_areas.index.map(tot_area_no_SOX2)
/tmp/ipykernel_1121090/4008132345.py:1: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  organoid_areas['SOX2_pos_area'] = organoid_areas.index.map(tot_area_SOX2)
/tmp/ipykernel_1121090/4008132345.py:2: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  organoid_areas['SOX2_neg_area'] = organoid_areas.index.map(tot_area_no_SOX2)
In [143]:
organoid_areas['SOX2_pos_area_over_tot'] = organoid_areas['SOX2_pos_area'] / organoid_areas['area_um2'] * 100
organoid_areas['SOX2_neg_area_over_tot'] = organoid_areas['SOX2_neg_area'] / organoid_areas['area_um2'] * 100
/tmp/ipykernel_1121090/1300664090.py:1: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  organoid_areas['SOX2_pos_area_over_tot'] = organoid_areas['SOX2_pos_area'] / organoid_areas['area_um2'] * 100
/tmp/ipykernel_1121090/1300664090.py:2: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  organoid_areas['SOX2_neg_area_over_tot'] = organoid_areas['SOX2_neg_area'] / organoid_areas['area_um2'] * 100
In [144]:
fig, ax = plt.subplots(1, 2, figsize=(20, 5))

organoid_area_mix1 = organoid_areas[~organoid_areas['SOX2_pos_area'].isna()]

for i, cond in enumerate(hormonal_conditions):
    data = organoid_area_mix1[(organoid_area_mix1['Line'] == 'CTL08A') & 
                                 (organoid_area_mix1['Condition'] == cond)]['nuclear_area']
    box = ax[0].boxplot(data, positions=[i], patch_artist=True, widths=.8)
    for patch in box['boxes']:
        patch.set_facecolor(color_palette[cond])
        box['medians'][0].set_color('black')
        # Add stripplot for CTL08A (single points per box)

    dmso_median = organoid_area_mix1[(organoid_area_mix1['Condition'] == 'DMSO') & 
                                       (organoid_area_mix1['Line'] == 'CTL08A')]['nuclear_area'].median()
    ax[0].axhline(dmso_median, color='red', linestyle='--', linewidth=.7, alpha=0.7)

sns.stripplot(
    data=organoid_area_mix1[(organoid_area_mix1['Line'] == 'CTL08A')],
    y='nuclear_area', x='Condition', order=hormonal_conditions, color='black', alpha=0.5, jitter=False, ax=ax[0], size=8
)

for i, cond in enumerate(hormonal_conditions):
    data = organoid_area_mix1[(organoid_area_mix1['Line'] == 'CTL04E') & 
                                 (organoid_area_mix1['Condition'] == cond)]['nuclear_area']
    box = ax[1].boxplot(data, positions=[i], patch_artist=True, widths=.8)
    for patch in box['boxes']:
        patch.set_facecolor(color_palette[cond])
        box['medians'][0].set_color('black')
    dmso_median = organoid_area_mix1[(organoid_area_mix1['Condition'] == 'DMSO') & 
                                       (organoid_area_mix1['Line'] == 'CTL04E')]['nuclear_area'].median()
    ax[1].axhline(dmso_median, color='red', linestyle='--', linewidth=.7, alpha=0.7)

sns.stripplot(
    data=organoid_area_mix1[(organoid_area_mix1['Line'] == 'CTL04E')],
    y='nuclear_area', x='Condition', order=hormonal_conditions, color='black', alpha=0.5, jitter=False, ax=ax[1], size=8
)

_ = ax[0].set_xticks(range(len(hormonal_conditions)))
_ = ax[0].set_xticklabels(hormonal_conditions, rotation=90)

_ = ax[1].set_xticks(range(len(hormonal_conditions)))
_ = ax[1].set_xticklabels(hormonal_conditions, rotation=90)

ax[0].set_title('CTL08A')
ax[1].set_title('CTL04E')

ax[0].set_ylabel('nuclear_area/organoid area')
ax[1].set_ylabel('nuclear_area/organoid area')
Out[144]:
Text(0, 0.5, 'nuclear_area/organoid area')
In [145]:
fig, ax = plt.subplots(1, 2, figsize=(20, 5))

organoid_area_mix1 = organoid_areas[~organoid_areas['SOX2_pos_area'].isna()]

for i, cond in enumerate(hormonal_conditions):
    data = organoid_area_mix1[(organoid_area_mix1['Line'] == 'CTL08A') & 
                                 (organoid_area_mix1['Condition'] == cond)]['SOX2_pos_area_over_tot']
    box = ax[0].boxplot(data, positions=[i], patch_artist=True, widths=.8)
    for patch in box['boxes']:
        patch.set_facecolor(color_palette[cond])
        box['medians'][0].set_color('black')
        # Add stripplot for CTL08A (single points per box)

    dmso_median = organoid_area_mix1[(organoid_area_mix1['Condition'] == 'DMSO') & 
                                       (organoid_area_mix1['Line'] == 'CTL08A')]['SOX2_pos_area_over_tot'].median()
    ax[0].axhline(dmso_median, color='red', linestyle='--', linewidth=.7, alpha=0.7)

sns.stripplot(
    data=organoid_area_mix1[(organoid_area_mix1['Line'] == 'CTL08A')],
    y='SOX2_pos_area_over_tot', x='Condition', order=hormonal_conditions, color='black', alpha=0.5, jitter=False, ax=ax[0], size=8
)

for i, cond in enumerate(hormonal_conditions):
    data = organoid_area_mix1[(organoid_area_mix1['Line'] == 'CTL04E') & 
                                 (organoid_area_mix1['Condition'] == cond)]['SOX2_pos_area_over_tot']
    box = ax[1].boxplot(data, positions=[i], patch_artist=True, widths=.8)
    for patch in box['boxes']:
        patch.set_facecolor(color_palette[cond])
        box['medians'][0].set_color('black')
    dmso_median = organoid_area_mix1[(organoid_area_mix1['Condition'] == 'DMSO') & 
                                       (organoid_area_mix1['Line'] == 'CTL04E')]['SOX2_pos_area_over_tot'].median()
    ax[1].axhline(dmso_median, color='red', linestyle='--', linewidth=.7, alpha=0.7)

sns.stripplot(
    data=organoid_area_mix1[(organoid_area_mix1['Line'] == 'CTL04E')],
    y='SOX2_pos_area_over_tot', x='Condition', order=hormonal_conditions, color='black', alpha=0.5, jitter=False, ax=ax[1], size=8
)

_ = ax[0].set_xticks(range(len(hormonal_conditions)))
_ = ax[0].set_xticklabels(hormonal_conditions, rotation=90)

_ = ax[1].set_xticks(range(len(hormonal_conditions)))
_ = ax[1].set_xticklabels(hormonal_conditions, rotation=90)

ax[0].set_title('CTL08A')
ax[1].set_title('CTL04E')

ax[0].set_ylabel('SOX2_pos_area_over_tot')
ax[1].set_ylabel('SOX2_pos_area_over_tot')
Out[145]:
Text(0, 0.5, 'SOX2_pos_area_over_tot')
In [146]:
fig, ax = plt.subplots(1, 2, figsize=(20, 5))

for i, cond in enumerate(hormonal_conditions):
    data = organoid_area_mix1[(organoid_area_mix1['Line'] == 'CTL08A') & 
                                 (organoid_area_mix1['Condition'] == cond)]['SOX2_neg_area_over_tot']
    box = ax[0].boxplot(data, positions=[i], patch_artist=True, widths=.8)
    for patch in box['boxes']:
        patch.set_facecolor(color_palette[cond])
        box['medians'][0].set_color('black')
        # Add stripplot for CTL08A (single points per box)

    dmso_median = organoid_area_mix1[(organoid_area_mix1['Condition'] == 'DMSO') & 
                                       (organoid_area_mix1['Line'] == 'CTL08A')]['SOX2_neg_area_over_tot'].median()
    ax[0].axhline(dmso_median, color='red', linestyle='--', linewidth=.7, alpha=0.7)

sns.stripplot(
    data=organoid_area_mix1[(organoid_area_mix1['Line'] == 'CTL08A')],
    y='SOX2_neg_area_over_tot', x='Condition', order=hormonal_conditions, color='black', alpha=0.5, jitter=False, ax=ax[0], size=8
)

for i, cond in enumerate(hormonal_conditions):
    data = organoid_area_mix1[(organoid_area_mix1['Line'] == 'CTL04E') & 
                                 (organoid_area_mix1['Condition'] == cond)]['SOX2_neg_area_over_tot']
    box = ax[1].boxplot(data, positions=[i], patch_artist=True, widths=.8)
    for patch in box['boxes']:
        patch.set_facecolor(color_palette[cond])
        box['medians'][0].set_color('black')
    dmso_median = organoid_area_mix1[(organoid_area_mix1['Condition'] == 'DMSO') & 
                                       (organoid_area_mix1['Line'] == 'CTL04E')]['SOX2_neg_area_over_tot'].median()
    ax[1].axhline(dmso_median, color='red', linestyle='--', linewidth=.7, alpha=0.7)

sns.stripplot(
    data=organoid_area_mix1[(organoid_area_mix1['Line'] == 'CTL04E')],
    y='SOX2_neg_area_over_tot', x='Condition', order=hormonal_conditions, color='black', alpha=0.5, jitter=False, ax=ax[1], size=8
)

_ = ax[0].set_xticks(range(len(hormonal_conditions)))
_ = ax[0].set_xticklabels(hormonal_conditions, rotation=90)

_ = ax[1].set_xticks(range(len(hormonal_conditions)))
_ = ax[1].set_xticklabels(hormonal_conditions, rotation=90)

ax[0].set_title('CTL08A')
ax[1].set_title('CTL04E')

ax[0].set_ylabel('SOX2_neg_area_over_tot')
ax[1].set_ylabel('SOX2_neg_area_over_tot')
Out[146]:
Text(0, 0.5, 'SOX2_neg_area_over_tot')