pc-123 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pc123/__init__.py +0 -0
- pc123/cli.py +23 -0
- pc123/experiments/exp1.py +21 -0
- pc123/experiments/exp2.py +25 -0
- pc123/experiments/exp3.py +39 -0
- pc123/experiments/exp4.py +47 -0
- pc123/experiments/exp5.py +54 -0
- pc123/experiments/exp6.py +45 -0
- pc123/experiments/exp7.py +34 -0
- pc_123-0.1.0.dist-info/METADATA +8 -0
- pc_123-0.1.0.dist-info/RECORD +15 -0
- pc_123-0.1.0.dist-info/WHEEL +5 -0
- pc_123-0.1.0.dist-info/entry_points.txt +2 -0
- pc_123-0.1.0.dist-info/licenses/LICENSE +0 -0
- pc_123-0.1.0.dist-info/top_level.txt +1 -0
pc123/__init__.py
ADDED
|
File without changes
|
pc123/cli.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from importlib import resources
|
|
3
|
+
|
|
4
|
+
def main():
|
|
5
|
+
if len(sys.argv) < 2:
|
|
6
|
+
print("Usage: pc-123 <experiment_number>")
|
|
7
|
+
return
|
|
8
|
+
|
|
9
|
+
num = sys.argv[1]
|
|
10
|
+
try:
|
|
11
|
+
code = resources.files("pc123.experiments").joinpath(f"exp{num}.py").read_text(encoding="utf-8")
|
|
12
|
+
except FileNotFoundError:
|
|
13
|
+
print(f"Experiment {num} not found.")
|
|
14
|
+
return
|
|
15
|
+
|
|
16
|
+
print("=" * 60)
|
|
17
|
+
print(f"# EXP{num}")
|
|
18
|
+
print("=" * 60)
|
|
19
|
+
print()
|
|
20
|
+
print(code)
|
|
21
|
+
|
|
22
|
+
if __name__ == "__main__":
|
|
23
|
+
main()
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Step 1: Import libraries
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
# Step 2: Load dataset
|
|
6
|
+
df = pd.read_csv('clinical_data.csv')
|
|
7
|
+
|
|
8
|
+
# Step 3: Display basic information
|
|
9
|
+
print("Dataset Shape:", df.shape)
|
|
10
|
+
print("\nFirst 5 Records:\n", df.head())
|
|
11
|
+
print("\nColumn Names:\n", df.columns.tolist())
|
|
12
|
+
print("\nData Types:\n", df.dtypes)
|
|
13
|
+
|
|
14
|
+
# Step 4: Generate summary statistics
|
|
15
|
+
print("\nDescriptive Statistics:\n", df.describe())
|
|
16
|
+
|
|
17
|
+
# Step 5: Check missing values
|
|
18
|
+
print("\nMissing Values:\n", df.isnull().sum())
|
|
19
|
+
|
|
20
|
+
print("\nDataset Information:")
|
|
21
|
+
df.info()
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Step 1: Import libraries
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
# Step 2: Load dataset
|
|
6
|
+
df = pd.read_csv('clinical_data.csv')
|
|
7
|
+
|
|
8
|
+
# Step 3: Check missing values
|
|
9
|
+
print("Missing Values:\n", df.isnull().sum())
|
|
10
|
+
|
|
11
|
+
# Step 4: Handle missing values
|
|
12
|
+
df_clean = df.dropna()
|
|
13
|
+
df_clean = df.fillna(df.mean(numeric_only=True))
|
|
14
|
+
|
|
15
|
+
# Step 5: Check and remove duplicate rows
|
|
16
|
+
print("Duplicate Rows:", df_clean.duplicated().sum())
|
|
17
|
+
df_clean.drop_duplicates(inplace=True)
|
|
18
|
+
|
|
19
|
+
# Step 6: Standardize inconsistent text entries
|
|
20
|
+
df_clean['gender'] = df_clean['gender'].str.strip().str.lower()
|
|
21
|
+
|
|
22
|
+
# Step 7: Verify cleaned dataset
|
|
23
|
+
print("\nMissing Values After Cleaning:\n", df_clean.isnull().sum())
|
|
24
|
+
print("Duplicate Rows After Cleaning:", df_clean.duplicated().sum())
|
|
25
|
+
df_clean.info()
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Step 1: Import libraries
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
# Step 2: Load datasets
|
|
6
|
+
demo = pd.read_csv('demographics.csv')
|
|
7
|
+
vitals = pd.read_csv('vitals.csv')
|
|
8
|
+
print(demo.shape, vitals.shape)
|
|
9
|
+
|
|
10
|
+
# Step 3: Check the key variable for duplicates
|
|
11
|
+
print("Duplicate IDs (demo):", demo['patient_id'].duplicated().sum())
|
|
12
|
+
print("Duplicate IDs (vitals):", vitals['patient_id'].duplicated().sum())
|
|
13
|
+
|
|
14
|
+
# Step 4: Merge datasets on patient_id
|
|
15
|
+
merged = pd.merge(demo, vitals, on='patient_id', how='inner') # how='left' keeps all patients from demo
|
|
16
|
+
print("Merged shape:", merged.shape)
|
|
17
|
+
|
|
18
|
+
# Step 5: Create derived variables
|
|
19
|
+
merged['bmi'] = (merged['weight_kg'] / (merged['height_cm'] / 100) ** 2).round(1)
|
|
20
|
+
|
|
21
|
+
merged['age_group'] = pd.cut(merged['age'],
|
|
22
|
+
bins=[0, 17, 39, 59, 120],
|
|
23
|
+
labels=['<18', '18-39', '40-59', '60+'])
|
|
24
|
+
|
|
25
|
+
merged['bmi_category'] = np.select(
|
|
26
|
+
[merged['bmi'] < 18.5, merged['bmi'] < 25, merged['bmi'] < 30],
|
|
27
|
+
['Underweight', 'Normal', 'Overweight'],
|
|
28
|
+
default='Obese')
|
|
29
|
+
|
|
30
|
+
merged['hypertension_flag'] = np.where(merged['sbp_baseline'] >= 140, 1, 0)
|
|
31
|
+
merged['sbp_change'] = merged['sbp_final'] - merged['sbp_baseline']
|
|
32
|
+
|
|
33
|
+
# Step 6: Verify the transformed dataset
|
|
34
|
+
print(merged[['patient_id', 'bmi', 'age_group', 'bmi_category',
|
|
35
|
+
'hypertension_flag', 'sbp_change']].head())
|
|
36
|
+
merged.info()
|
|
37
|
+
|
|
38
|
+
# Step 7: Save the transformed dataset
|
|
39
|
+
merged.to_csv('clinical_transformed.csv', index=False)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Step 1: Import libraries
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import matplotlib.pyplot as plt
|
|
4
|
+
|
|
5
|
+
# Step 2: Load the transformed dataset
|
|
6
|
+
df = pd.read_csv('clinical_transformed.csv')
|
|
7
|
+
|
|
8
|
+
# Step 3: Bar chart - number of patients in each BMI category
|
|
9
|
+
counts = df['bmi_category'].value_counts()
|
|
10
|
+
plt.figure(figsize=(6, 4))
|
|
11
|
+
plt.bar(counts.index, counts.values, color='steelblue')
|
|
12
|
+
plt.title('Patients by BMI Category')
|
|
13
|
+
plt.xlabel('BMI Category')
|
|
14
|
+
plt.ylabel('Number of Patients')
|
|
15
|
+
plt.tight_layout()
|
|
16
|
+
plt.savefig('bar_bmi_category.png')
|
|
17
|
+
plt.show()
|
|
18
|
+
|
|
19
|
+
# Step 4: Histogram - distribution of age
|
|
20
|
+
plt.figure(figsize=(6, 4))
|
|
21
|
+
plt.hist(df['age'], bins=10, color='skyblue', edgecolor='black')
|
|
22
|
+
plt.title('Age Distribution of Patients')
|
|
23
|
+
plt.xlabel('Age (years)')
|
|
24
|
+
plt.ylabel('Frequency')
|
|
25
|
+
plt.tight_layout()
|
|
26
|
+
plt.savefig('hist_age.png')
|
|
27
|
+
plt.show()
|
|
28
|
+
|
|
29
|
+
# Step 5: Box plot - baseline systolic BP by gender
|
|
30
|
+
df.boxplot(column='sbp_baseline', by='gender', figsize=(6, 4))
|
|
31
|
+
plt.title('Baseline Systolic BP by Gender')
|
|
32
|
+
plt.suptitle('')
|
|
33
|
+
plt.xlabel('Gender')
|
|
34
|
+
plt.ylabel('Systolic BP (mmHg)')
|
|
35
|
+
plt.tight_layout()
|
|
36
|
+
plt.savefig('box_sbp_gender.png')
|
|
37
|
+
plt.show()
|
|
38
|
+
|
|
39
|
+
# Step 6: Compare patient groups - mean change in systolic BP by gender
|
|
40
|
+
print(df.groupby('gender')['sbp_change'].describe())
|
|
41
|
+
|
|
42
|
+
df.groupby('gender')['sbp_change'].mean().plot(kind='bar', color='steelblue')
|
|
43
|
+
plt.title('Mean Change in Systolic BP by Gender')
|
|
44
|
+
plt.xlabel('Gender')
|
|
45
|
+
plt.ylabel('Mean Change (mmHg)')
|
|
46
|
+
plt.tight_layout()
|
|
47
|
+
plt.show()
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
|
|
3
|
+
# Step 1: Load raw datasets
|
|
4
|
+
raw_demo = pd.read_csv('demographics.csv')
|
|
5
|
+
raw_vitals = pd.read_csv('vitals.csv') # columns: patient_id, visit, sbp
|
|
6
|
+
|
|
7
|
+
STUDY = 'STUDY01'
|
|
8
|
+
|
|
9
|
+
# Step 2: Create DM (Demographics) domain - one row per subject
|
|
10
|
+
dm = pd.DataFrame({
|
|
11
|
+
'STUDYID': STUDY,
|
|
12
|
+
'DOMAIN': 'DM',
|
|
13
|
+
'USUBJID': STUDY + '-' + raw_demo['patient_id'].astype(str),
|
|
14
|
+
'SUBJID': raw_demo['patient_id'].astype(str),
|
|
15
|
+
'AGE': raw_demo['age'],
|
|
16
|
+
'AGEU': 'YEARS',
|
|
17
|
+
'SEX': raw_demo['gender'].str.strip().str.upper().str[0]
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
# Step 3: Create VS (Vital Signs) domain - one record per test per visit
|
|
21
|
+
vs = pd.DataFrame({
|
|
22
|
+
'STUDYID': STUDY,
|
|
23
|
+
'DOMAIN': 'VS',
|
|
24
|
+
'USUBJID': STUDY + '-' + raw_vitals['patient_id'].astype(str),
|
|
25
|
+
'VSTESTCD': 'SYSBP',
|
|
26
|
+
'VSTEST': 'Systolic Blood Pressure',
|
|
27
|
+
'VSSTRESN': raw_vitals['sbp'],
|
|
28
|
+
'VSSTRESU': 'mmHg',
|
|
29
|
+
'VISIT': raw_vitals['visit'].str.strip().str.upper()
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
# Visit order: add every visit name that appears in your data
|
|
33
|
+
visit_order = {'SCREENING': 0, 'BASELINE': 1, 'WEEK 4': 2, 'WEEK 8': 3, 'WEEK 12': 4}
|
|
34
|
+
vs['VISITNUM'] = vs['VISIT'].map(visit_order)
|
|
35
|
+
vs['VSBLFL'] = vs['VISIT'].eq('BASELINE').map({True: 'Y', False: ''})
|
|
36
|
+
|
|
37
|
+
vs = vs.sort_values(['USUBJID', 'VISITNUM']).reset_index(drop=True)
|
|
38
|
+
vs['VSSEQ'] = vs.groupby('USUBJID').cumcount() + 1
|
|
39
|
+
|
|
40
|
+
# Step 4: Variable labels
|
|
41
|
+
dm.attrs['labels'] = {
|
|
42
|
+
'STUDYID': 'Study Identifier', 'DOMAIN': 'Domain Abbreviation',
|
|
43
|
+
'USUBJID': 'Unique Subject Identifier', 'SUBJID': 'Subject Identifier for the Study',
|
|
44
|
+
'AGE': 'Age', 'AGEU': 'Age Units', 'SEX': 'Sex'}
|
|
45
|
+
vs.attrs['labels'] = {
|
|
46
|
+
'STUDYID': 'Study Identifier', 'DOMAIN': 'Domain Abbreviation',
|
|
47
|
+
'USUBJID': 'Unique Subject Identifier', 'VSSEQ': 'Sequence Number',
|
|
48
|
+
'VSTESTCD': 'Vital Signs Test Short Name', 'VSTEST': 'Vital Signs Test Name',
|
|
49
|
+
'VSSTRESN': 'Numeric Result/Finding in Standard Units', 'VSSTRESU': 'Standard Units',
|
|
50
|
+
'VSBLFL': 'Baseline Flag', 'VISITNUM': 'Visit Number', 'VISIT': 'Visit Name'}
|
|
51
|
+
|
|
52
|
+
# Step 5: Save
|
|
53
|
+
dm.to_csv('dm.csv', index=False)
|
|
54
|
+
vs.to_csv('vs.csv', index=False)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Step 1: Import libraries
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
# Step 2: Load SDTM datasets (from Experiment 5)
|
|
6
|
+
dm = pd.read_csv('dm.csv')
|
|
7
|
+
vs = pd.read_csv('vs.csv')
|
|
8
|
+
|
|
9
|
+
# Step 3: Create ADSL (Subject-Level Analysis Dataset)
|
|
10
|
+
adsl = dm.drop(columns='DOMAIN')
|
|
11
|
+
adsl['AGEGR1'] = pd.cut(adsl['AGE'],
|
|
12
|
+
bins=[0, 39, 59, 200],
|
|
13
|
+
labels=['<40', '40-59', '>=60']).astype(str)
|
|
14
|
+
adsl['SAFFL'] = 'Y'
|
|
15
|
+
|
|
16
|
+
# Step 4: Create ADVS (Vital Signs Analysis Dataset)
|
|
17
|
+
advs = vs.merge(adsl[['USUBJID', 'AGE', 'SEX', 'AGEGR1', 'SAFFL']],
|
|
18
|
+
on='USUBJID', how='left')
|
|
19
|
+
advs['PARAMCD'] = advs['VSTESTCD']
|
|
20
|
+
advs['PARAM'] = advs['VSTEST'] + ' (' + advs['VSSTRESU'] + ')'
|
|
21
|
+
advs['AVAL'] = advs['VSSTRESN']
|
|
22
|
+
advs['AVISIT'] = advs['VISIT']
|
|
23
|
+
advs['AVISITN'] = advs['VISIT'].map({'BASELINE': 0, 'WEEK 12': 12})
|
|
24
|
+
advs['ABLFL'] = np.where(advs['VISIT'] == 'BASELINE', 'Y', '')
|
|
25
|
+
|
|
26
|
+
# Step 5: Derive baseline, change from baseline and percent change
|
|
27
|
+
base = advs.loc[advs['ABLFL'] == 'Y', ['USUBJID', 'PARAMCD', 'AVAL']]
|
|
28
|
+
base = base.rename(columns={'AVAL': 'BASE'})
|
|
29
|
+
advs = advs.merge(base, on=['USUBJID', 'PARAMCD'], how='left')
|
|
30
|
+
advs['CHG'] = advs['AVAL'] - advs['BASE']
|
|
31
|
+
advs['PCHG'] = (advs['CHG'] / advs['BASE'] * 100).round(1)
|
|
32
|
+
advs.loc[advs['ABLFL'] == 'Y', ['CHG', 'PCHG']] = np.nan # post-baseline only
|
|
33
|
+
|
|
34
|
+
# Step 6: Select variables and verify
|
|
35
|
+
advs = advs[['STUDYID', 'USUBJID', 'AGE', 'SEX', 'AGEGR1', 'SAFFL',
|
|
36
|
+
'PARAMCD', 'PARAM', 'AVISIT', 'AVISITN',
|
|
37
|
+
'AVAL', 'ABLFL', 'BASE', 'CHG', 'PCHG']]
|
|
38
|
+
advs = advs.sort_values(['USUBJID', 'AVISITN']).reset_index(drop=True)
|
|
39
|
+
print(adsl.head())
|
|
40
|
+
print(advs.head())
|
|
41
|
+
advs.info()
|
|
42
|
+
|
|
43
|
+
# Step 7: Save the ADaM datasets
|
|
44
|
+
adsl.to_csv('adsl.csv', index=False)
|
|
45
|
+
advs.to_csv('advs.csv', index=False)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Step 1: Import libraries
|
|
2
|
+
import pandas as pd
|
|
3
|
+
from scipy import stats
|
|
4
|
+
from statsmodels.stats.multicomp import pairwise_tukeyhsd
|
|
5
|
+
|
|
6
|
+
# Step 2: Load the ADaM dataset (from Experiment 6)
|
|
7
|
+
advs = pd.read_csv('advs.csv')
|
|
8
|
+
post = advs[advs['AVISIT'] == 'WEEK 12'].dropna(subset=['CHG']).copy()
|
|
9
|
+
|
|
10
|
+
# Step 3: Descriptive statistics by group
|
|
11
|
+
print(post.groupby('SEX')['CHG'].describe())
|
|
12
|
+
print(post.groupby('AGEGR1')['CHG'].describe())
|
|
13
|
+
|
|
14
|
+
# Step 4: Paired t-test - baseline vs. Week 12 systolic BP
|
|
15
|
+
t_stat, p_val = stats.ttest_rel(post['BASE'], post['AVAL'])
|
|
16
|
+
print("Paired t-test: t =", round(t_stat, 3), ", p =", round(p_val, 4))
|
|
17
|
+
|
|
18
|
+
# Step 5: Independent t-test - change from baseline, male vs. female
|
|
19
|
+
male = post.loc[post['SEX'] == 'M', 'CHG']
|
|
20
|
+
female = post.loc[post['SEX'] == 'F', 'CHG']
|
|
21
|
+
t_stat, p_val = stats.ttest_ind(male, female, equal_var=False)
|
|
22
|
+
print("Independent t-test: t =", round(t_stat, 3), ", p =", round(p_val, 4))
|
|
23
|
+
|
|
24
|
+
# Step 6: One-way ANOVA - change from baseline across age groups
|
|
25
|
+
groups = [g['CHG'].values for _, g in post.groupby('AGEGR1')]
|
|
26
|
+
f_stat, p_val = stats.f_oneway(*groups)
|
|
27
|
+
print("ANOVA: F =", round(f_stat, 3), ", p =", round(p_val, 4))
|
|
28
|
+
|
|
29
|
+
# Step 7: Post-hoc test (Tukey HSD) between age groups
|
|
30
|
+
print(pairwise_tukeyhsd(post['CHG'], post['AGEGR1']))
|
|
31
|
+
|
|
32
|
+
# Step 8: Interpret the results
|
|
33
|
+
alpha = 0.05
|
|
34
|
+
print("Significant at 5% level" if p_val < alpha else "Not significant at 5% level")
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
pc123/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
pc123/cli.py,sha256=6JUp2biY5VmSFrFwzjqThMP9j3APLLeMKLY2VPFVXQw,530
|
|
3
|
+
pc123/experiments/exp1.py,sha256=EUj_2PARG6m_ampikz6FZJTfDiRFiahyGf7rOUM5YVQ,557
|
|
4
|
+
pc123/experiments/exp2.py,sha256=daZm_sWS8JVV9rGoDS4Bp_h8OfinZQRtB514R_xiZao,771
|
|
5
|
+
pc123/experiments/exp3.py,sha256=O5sROsmye_1Ba97M9fB2D03lN7e8itVC6YzrjSXvalE,1487
|
|
6
|
+
pc123/experiments/exp4.py,sha256=LBeYsiP2uqRVY1kF1QpoCJGOvG1mCqWSspNsucRLHYM,1481
|
|
7
|
+
pc123/experiments/exp5.py,sha256=kavlR8KNVC3v0xjEtMAsToSgyLQBHwxqJu9jS3s23-k,2126
|
|
8
|
+
pc123/experiments/exp6.py,sha256=T17IF3bzZEfe3oz_MFvpWHCCl7JwN6RZfAimiAm8LHw,1801
|
|
9
|
+
pc123/experiments/exp7.py,sha256=ubIA2FWwflJmtf_M8yTjUFwhOO9NfbDtXtdErLk3CBI,1415
|
|
10
|
+
pc_123-0.1.0.dist-info/licenses/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
pc_123-0.1.0.dist-info/METADATA,sha256=oN5eO4-DwhSkKwQj_c2CDzrZ6XBvCvxIY0isCQ6CA90,201
|
|
12
|
+
pc_123-0.1.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
13
|
+
pc_123-0.1.0.dist-info/entry_points.txt,sha256=MmAoODyMpDaPnpZ5Dbb_KTC0SDksTV7dq0SGaybSAYo,42
|
|
14
|
+
pc_123-0.1.0.dist-info/top_level.txt,sha256=MEdVBGA4U2YXsFUuLXpBXnGIvkzXGYuPqSatGpXrnSE,6
|
|
15
|
+
pc_123-0.1.0.dist-info/RECORD,,
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pc123
|