In this activity, we will explore real clinical data from breast cancer patients in The Cancer Genome Atlas (TCGA).
The goal is not only to learn Python. The goal is to use Python to ask scientific questions:
Later in DREAM-High, we will connect this type of clinical information to gene expression data. That is where computational biology becomes especially powerful: we can ask how molecular patterns relate to patient and tumor characteristics.
Before editing, use:
File → Save As…
Save a copy with your name in the file name.
Example:
Breast_Cancer_Patient_Data_Diana_Python.Rmd
This gives you your own workbook while preserving the original.
This activity uses:
brca_clin.csv — a simplified TCGA breast cancer
clinical datasetduct_cells.jpg — a schematic of breast duct
changesmetastasis.jpg — a schematic of metastatic spreadThe files should be located in the shared data
directory.
# Import the Python libraries used in this activity.
import os
import pandas as pd
import matplotlib.pyplot as plt
# Location of the data files on the DREAM-High server.
data_dir = "/shared/dreamhigh/data"
# Make figures a little larger.
plt.rcParams["figure.figsize"] = (8,5)
Breast cancer often begins in the ducts or lobules of the breast. As cancer develops, cells can grow abnormally, fill spaces where they do not belong, and eventually invade nearby tissue.
The figure below shows a simplified progression from a normal duct to invasive ductal cancer.
## <Figure size 600x400 with 0 Axes>
## <matplotlib.image.AxesImage object at 0x7898544a1610>
## (-0.5, 1302.5, 459.5, -0.5)
Think before moving on:
What do you notice as the duct changes from normal tissue to invasive cancer?
Write one observation here:
Your answer: The cell become and also break through the duct to spread.
A major danger of cancer is metastasis: cancer cells can leave the original tumor, move through the body, and grow in distant organs.
## <Figure size 600x400 with 0 Axes>
## <matplotlib.image.AxesImage object at 0x789854526930>
## (-0.5, 1220.5, 778.5, -0.5)
Cancer metastasis is a multi-step process in which tumor cells spread from the primary tumor to distant organs.
The figure also highlights important biological processes involved in metastasis, including:
Metastasis is biologically complex and difficult to predict or treat.
In clinical cancer datasets, we often look for features that tell us something about tumor severity, spread, treatment, or patient outcome.
We will use a CSV file. CSV stands for comma-separated values. It is a plain-text table format that can be opened in Python, R, Excel, or many other programs.
brca_clin_df = pd.read_csv(os.path.join(data_dir, "brca_clin.csv"))
The object name brca_clin_df is descriptive:
brca = breast cancerclin = clinical datadf = data frameA data frame is a table:
brca_clin_df.shape
## (1082, 27)
The first number is the number of rows. The second number is the number of columns.
n_patients = brca_clin_df.shape[0]
n_features = brca_clin_df.shape[1]
print("This dataset contains", n_patients, "patients and", n_features, "clinical features.")
## This dataset contains 1082 patients and 27 clinical features.
brca_clin_df.columns.tolist()
## ['bcr_patient_barcode', 'gender', 'race', 'ethnicity', 'age_at_diagnosis', 'year_of_initial_pathologic_diagnosis', 'vital_status', 'menopause_status', 'tumor_status', 'margin_status', 'days_to_last_followup', 'prior_dx', 'new_tumor_event_after_initial_treatment', 'radiation_therapy', 'histological_type', 'pathologic_T', 'pathologic_M', 'pathologic_N', 'pathologic_stage_sub', 'pathologic_stage', 'lymph_node_examined_count', 'number_of_lymphnodes_positive', 'initial_diagnosis_method', 'surgical_procedure', 'estrogen_receptor_status', 'progesterone_receptor_status', 'her2_receptor_status']
Question: Which column names look familiar? Which ones are confusing?
Your answer: The column “age” looks familiar because it tells us the patiants age, while some of the abbreviated medcial terms are cofusing.
brca_clin_df.head()
## bcr_patient_barcode gender ... progesterone_receptor_status her2_receptor_status
## 0 TCGA-3C-AAAU FEMALE ... Positive Negative
## 1 TCGA-3C-AALI FEMALE ... Positive Positive
## 2 TCGA-3C-AALJ FEMALE ... Positive Indeterminate
## 3 TCGA-3C-AALK FEMALE ... Positive Positive
## 4 TCGA-4H-AAAK FEMALE ... Positive Equivocal
##
## [5 rows x 27 columns]
For a larger view, you can run this interactively in the Console:
View(brca_clin_df)
brca_clin_df.iloc[0, 0]
## 'TCGA-3C-AAAU'
The first number selects the row. The second number selects the column.
Important Python note: Python starts counting at 0. So row 0 is the first row, and column 0 is the first column.
brca_clin_df.iloc[[0]].T
## 0
## bcr_patient_barcode TCGA-3C-AAAU
## gender FEMALE
## race WHITE
## ethnicity NOT HISPANIC OR LATINO
## age_at_diagnosis 55.0
## year_of_initial_pathologic_diagnosis 2004
## vital_status Alive
## menopause_status Pre (<6 months since LMP AND no prior bilatera...
## tumor_status WITH TUMOR
## margin_status Negative
## days_to_last_followup 4047
## prior_dx No
## new_tumor_event_after_initial_treatment NO
## radiation_therapy NO
## histological_type Infiltrating Lobular Carcinoma
## pathologic_T TX
## pathologic_M MX
## pathologic_N NX
## pathologic_stage_sub Stage X
## pathologic_stage X
## lymph_node_examined_count 13
## number_of_lymphnodes_positive 4
## initial_diagnosis_method [Not Available]
## surgical_procedure Modified Radical Mastectomy
## estrogen_receptor_status Positive
## progesterone_receptor_status Positive
## her2_receptor_status Negative
The .T transposes the row so it is easier to read.
brca_clin_df.iloc[[0, 1, 2], 0:6]
## bcr_patient_barcode ... year_of_initial_pathologic_diagnosis
## 0 TCGA-3C-AAAU ... 2004
## 1 TCGA-3C-AALI ... 2003
## 2 TCGA-3C-AALJ ... 2011
##
## [3 rows x 6 columns]
Prediction question: What do you think this code will show?
brca_clin_df.loc[[9, 19, 29], ["gender", "age_at_diagnosis", "vital_status"]]
## gender age_at_diagnosis vital_status
## 9 FEMALE 56.0 Alive
## 19 FEMALE 40.0 Alive
## 29 FEMALE 34.0 Alive
Your answer: i think this code will show age at diagnosis vitial status and gender.
Clinical data are not perfectly clean. Some values are missing, unknown, not evaluated, or not available.
Let us look for values that indicate missing or uncertain information.
missing_like_values = ["[Not Available]", "[Not Evaluated]", "[Unknown]", "Indeterminate", "[Discrepancy]", ""]
missing_like_count = []
for column in brca_clin_df.columns:
count = brca_clin_df[column].isin(missing_like_values).sum() + brca_clin_df[column].isna().sum()
missing_like_count.append(count)
missing_summary = pd.DataFrame({
"feature": brca_clin_df.columns,
"missing_or_uncertain_count": missing_like_count,
"percent": [round(100 * count / len(brca_clin_df), 1) for count in missing_like_count]
})
missing_summary
## feature ... percent
## 0 bcr_patient_barcode ... 0.0
## 1 gender ... 0.0
## 2 race ... 8.3
## 3 ethnicity ... 15.6
## 4 age_at_diagnosis ... 0.1
## 5 year_of_initial_pathologic_diagnosis ... 0.2
## 6 vital_status ... 0.0
## 7 menopause_status ... 8.0
## 8 tumor_status ... 3.3
## 9 margin_status ... 6.1
## 10 days_to_last_followup ... 9.5
## 11 prior_dx ... 0.1
## 12 new_tumor_event_after_initial_treatment ... 18.3
## 13 radiation_therapy ... 9.1
## 14 histological_type ... 0.1
## 15 pathologic_T ... 0.0
## 16 pathologic_M ... 0.0
## 17 pathologic_N ... 0.0
## 18 pathologic_stage_sub ... 0.5
## 19 pathologic_stage ... 0.0
## 20 lymph_node_examined_count ... 11.2
## 21 number_of_lymphnodes_positive ... 15.1
## 22 initial_diagnosis_method ... 8.6
## 23 surgical_procedure ... 5.1
## 24 estrogen_receptor_status ... 4.6
## 25 progesterone_receptor_status ... 4.9
## 26 her2_receptor_status ... 17.6
##
## [27 rows x 3 columns]
ordered_missing = missing_summary.sort_values("missing_or_uncertain_count", ascending=False)
plt.figure(figsize=(10, 5))
plt.bar(ordered_missing["feature"], ordered_missing["missing_or_uncertain_count"])
plt.title("Missing or uncertain values by clinical feature")
plt.ylabel("Number of patients")
plt.xticks(rotation=90)
## ([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26], [Text(0, 0, 'new_tumor_event_after_initial_treatment'), Text(1, 0, 'her2_receptor_status'), Text(2, 0, 'ethnicity'), Text(3, 0, 'number_of_lymphnodes_positive'), Text(4, 0, 'lymph_node_examined_count'), Text(5, 0, 'days_to_last_followup'), Text(6, 0, 'radiation_therapy'), Text(7, 0, 'initial_diagnosis_method'), Text(8, 0, 'race'), Text(9, 0, 'menopause_status'), Text(10, 0, 'margin_status'), Text(11, 0, 'surgical_procedure'), Text(12, 0, 'progesterone_receptor_status'), Text(13, 0, 'estrogen_receptor_status'), Text(14, 0, 'tumor_status'), Text(15, 0, 'pathologic_stage_sub'), Text(16, 0, 'year_of_initial_pathologic_diagnosis'), Text(17, 0, 'age_at_diagnosis'), Text(18, 0, 'prior_dx'), Text(19, 0, 'histological_type'), Text(20, 0, 'pathologic_N'), Text(21, 0, 'pathologic_stage'), Text(22, 0, 'pathologic_M'), Text(23, 0, 'pathologic_T'), Text(24, 0, 'gender'), Text(25, 0, 'vital_status'), Text(26, 0, 'bcr_patient_barcode')])
plt.tight_layout()
plt.show()
Question: Which clinical features have the most missing or uncertain information?
Your answer: The features with the most missing and uncertain information are brain tumors,cancer subtype and treatment.
This is an important lesson: real biomedical datasets are powerful, but they are also imperfect.
A categorical variable describes groups or labels.
Examples:
In pandas, the function .value_counts() counts how many
times each value appears.
brca_clin_df["gender"].value_counts(dropna=False)
## gender
## FEMALE 1070
## MALE 12
## Name: count, dtype: int64
#Fixed this graph | no longer contains an overlapping on the female’s data
import matplotlib.pyplot as plt
# Count the number of patients in each gender category
gender_counts = brca_clin_df["gender"].value_counts()
# Create the bar plot
plt.figure(figsize=(4, 3))
plt.bar(
gender_counts.index,
gender_counts.values,
width=0.5,
color="lightgray", # Gray bars like R
edgecolor="black", # Black outlines
linewidth=1.2 # Thicker outlines
)
plt.title("Gender distribution in the TCGA breast cancer cohort")
plt.xlabel("Gender")
plt.ylabel("Number of patients")
plt.tight_layout()
plt.show()
Question: What does this tell us about the cohort?
Your answer: The cohort is mostly filled with female patients of different ages, with both living and deceased patients.
brca_clin_df["race"].value_counts(dropna=False)
## race
## WHITE 747
## BLACK OR AFRICAN AMERICAN 183
## [Not Available] 87
## ASIAN 61
## [Not Evaluated] 3
## AMERICAN INDIAN OR ALASKA NATIVE 1
## Name: count, dtype: int64
race_counts = brca_clin_df["race"].value_counts(dropna=False)
race_counts.plot(kind="bar")
plt.title("Race distribution")
plt.ylabel("Number of patients")
plt.xlabel("Race")
plt.xticks(rotation=90)
## (array([0, 1, 2, 3, 4, 5]), [Text(0, 0, 'WHITE'), Text(1, 0, 'BLACK OR AFRICAN AMERICAN'), Text(2, 0, '[Not Available]'), Text(3, 0, 'ASIAN'), Text(4, 0, '[Not Evaluated]'), Text(5, 0, 'AMERICAN INDIAN OR ALASKA NATIVE')])
plt.tight_layout()
plt.show()
Discussion question: Why is it important to look at who is represented in a biomedical dataset?
Your answer: It is important because the dataset should represent different groups so the results are fair and accurate for everyone.
A numerical variable contains numbers.
Example:
brca_clin_df["age_at_diagnosis"].describe()
## count 1081.000000
## mean 58.394080
## std 13.206204
## min 26.000000
## 25% 49.000000
## 50% 58.000000
## 75% 67.000000
## max 90.000000
## Name: age_at_diagnosis, dtype: float64
import matplotlib.pyplot as plt
plt.figure(figsize=(4, 3))
plt.hist(
brca_clin_df["age_at_diagnosis"],
bins=13,
edgecolor="black", # Black outlines
linewidth=0.9 # Thicker outlines
)
plt.title("Age at diagnosis")
plt.xlabel("Age at diagnosis")
plt.ylabel("Number of patients")
plt.xticks([30, 40, 50, 60, 70, 80, 90])
## ([<matplotlib.axis.XTick object at 0x789853a635f0>, <matplotlib.axis.XTick object at 0x7898540c1be0>, <matplotlib.axis.XTick object at 0x789854222ff0>, <matplotlib.axis.XTick object at 0x7898540e7c80>, <matplotlib.axis.XTick object at 0x7898541e5a00>, <matplotlib.axis.XTick object at 0x7898541e54c0>, <matplotlib.axis.XTick object at 0x78985435a1b0>], [Text(30, 0, '30'), Text(40, 0, '40'), Text(50, 0, '50'), Text(60, 0, '60'), Text(70, 0, '70'), Text(80, 0, '80'), Text(90, 0, '90')])
plt.tight_layout()
plt.show()
Question: Around what ages are most patients diagnosed in this dataset?
Your answer: Most patients were diagnosed around 50 to 70 years old.
Breast cancer tumors are often tested for receptors that can influence growth and treatment.
Important receptor features include:
brca_clin_df["estrogen_receptor_status"].value_counts(dropna=False)
## estrogen_receptor_status
## Positive 796
## Negative 236
## [Not Evaluated] 48
## Indeterminate 2
## Name: count, dtype: int64
er_counts = brca_clin_df["estrogen_receptor_status"].value_counts(dropna=False)
er_counts.plot(kind="bar")
plt.title("Estrogen receptor status")
plt.ylabel("Number of patients")
plt.xlabel("ER status")
plt.xticks(rotation=90)
## (array([0, 1, 2, 3]), [Text(0, 0, 'Positive'), Text(1, 0, 'Negative'), Text(2, 0, '[Not Evaluated]'), Text(3, 0, 'Indeterminate')])
plt.tight_layout()
plt.show()
brca_clin_df["progesterone_receptor_status"].value_counts(dropna=False)
## progesterone_receptor_status
## Positive 689
## Negative 340
## [Not Evaluated] 49
## Indeterminate 4
## Name: count, dtype: int64
brca_clin_df["her2_receptor_status"].value_counts(dropna=False)
## her2_receptor_status
## Negative 554
## Equivocal 177
## [Not Evaluated] 170
## Positive 161
## Indeterminate 12
## [Not Available] 8
## Name: count, dtype: int64
A powerful part of data analysis is selecting patients who meet more than one condition.
For example, how many patients are both ER-positive and PR-positive?
ERpos_PRpos = brca_clin_df[
(brca_clin_df["estrogen_receptor_status"] == "Positive") &
(brca_clin_df["progesterone_receptor_status"] == "Positive")
]
len(ERpos_PRpos)
## 672
print("There are", len(ERpos_PRpos), "patients with both ER-positive and PR-positive tumors.")
## There are 672 patients with both ER-positive and PR-positive tumors.
Triple-negative breast cancer means the tumor is:
These cancers can be more difficult to treat because they do not have these common therapeutic targets.
tnbc = brca_clin_df[
(brca_clin_df["estrogen_receptor_status"] == "Negative") &
(brca_clin_df["progesterone_receptor_status"] == "Negative") &
(brca_clin_df["her2_receptor_status"] == "Negative")
]
len(tnbc)
## 114
tnbc_percent = round(100 * len(tnbc) / len(brca_clin_df), 1)
print("Triple-negative breast cancer cases make up", tnbc_percent, "% of this dataset.")
## Triple-negative breast cancer cases make up 10.5 % of this dataset.
So far, we summarized one variable at a time. But clinical questions often involve relationships between variables.
er_vital_table = pd.crosstab(
brca_clin_df["estrogen_receptor_status"],
brca_clin_df["vital_status"]
)
er_vital_table
## vital_status Alive Dead
## estrogen_receptor_status
## Indeterminate 0 2
## Negative 195 41
## Positive 697 99
## [Not Evaluated] 40 8
Add row percentages:
round(100 * er_vital_table.div(er_vital_table.sum(axis=1), axis=0), 1)
## vital_status Alive Dead
## estrogen_receptor_status
## Indeterminate 0.0 100.0
## Negative 82.6 17.4
## Positive 87.6 12.4
## [Not Evaluated] 83.3 16.7
This table asks:
Within each ER status group, what percent of patients are alive or dead?
A stacked bar plot is a useful Python visualization for two categorical variables.
er_vital_percent = 100 * er_vital_table.div(er_vital_table.sum(axis=1), axis=0)
er_vital_percent.plot(kind="bar", stacked=True)
plt.title("Estrogen receptor status and vital status")
plt.xlabel("Estrogen receptor status")
plt.ylabel("Percent of patients")
plt.xticks(rotation=45)
## (array([0, 1, 2, 3]), [Text(0, 0, 'Indeterminate'), Text(1, 0, 'Negative'), Text(2, 0, 'Positive'), Text(3, 0, '[Not Evaluated]')])
plt.legend(title="Vital status")
plt.tight_layout()
plt.show()
Question: What can you conclude from this plot? What should you be careful not to overclaim?
Your answer: The plot can show the differences in vital status by estrogen receptor status but it does not prove that estrogen receptor status causes difference in survival.
Histology describes what the tumor tissue looks like under a microscope.
histology_counts = brca_clin_df["histological_type"].value_counts(dropna=False)
histology_counts
## histological_type
## Infiltrating Ductal Carcinoma 774
## Infiltrating Lobular Carcinoma 201
## Other, specify 45
## Mixed Histology (please specify) 29
## Mucinous Carcinoma 17
## Metaplastic Carcinoma 8
## Medullary Carcinoma 6
## Infiltrating Carcinoma NOS 1
## [Not Available] 1
## Name: count, dtype: int64
histology_counts.plot(kind="bar")
plt.title("Histological type")
plt.ylabel("Number of patients")
plt.xlabel("Histological type")
plt.xticks(rotation=90)
## (array([0, 1, 2, 3, 4, 5, 6, 7, 8]), [Text(0, 0, 'Infiltrating Ductal Carcinoma'), Text(1, 0, 'Infiltrating Lobular Carcinoma'), Text(2, 0, 'Other, specify'), Text(3, 0, 'Mixed Histology (please specify)'), Text(4, 0, 'Mucinous Carcinoma'), Text(5, 0, 'Metaplastic Carcinoma'), Text(6, 0, 'Medullary Carcinoma'), Text(7, 0, 'Infiltrating Carcinoma NOS'), Text(8, 0, '[Not Available]')])
plt.tight_layout()
plt.show()
Most tumors in this dataset are infiltrating ductal carcinoma or infiltrating lobular carcinoma.
N_ductal = histology_counts.get("Infiltrating Ductal Carcinoma", 0)
percent_ductal = round(100 * N_ductal / len(brca_clin_df), 1)
print(str(percent_ductal) + "% of patients have infiltrating ductal carcinoma.")
## 71.5% of patients have infiltrating ductal carcinoma.
Pathologic stage describes how far the cancer has progressed.
brca_clin_df["pathologic_stage"].value_counts(dropna=False).sort_index()
## pathologic_stage
## I 179
## II 615
## III 250
## IV 19
## X 14
## [Not vailable] 5
## Name: count, dtype: int64
How many patients have stage II breast cancer?
stage_counts = brca_clin_df["pathologic_stage"].value_counts(dropna=False)
N_stageII = stage_counts.get("II", 0)
N_stageII
## 615
percent_stageII = round(100 * N_stageII / len(brca_clin_df), 1)
print(
"There are",
N_stageII,
"patients with stage II breast cancer, representing",
percent_stageII,
"% of the cohort."
)
## There are 615 patients with stage II breast cancer, representing 56.8 % of the cohort.
Make a bar plot of cancer stage.
#Fixed bar plot (original plot showcased stage I as the highest)
import matplotlib.pyplot as plt
# Count the number of patients in each pathologic stage
stage_counts = brca_clin_df["pathologic_stage"].value_counts().sort_index()
# Create the bar plot
plt.figure(figsize=(5, 4))
plt.bar(
stage_counts.index,
stage_counts.values,
color="lightgray",
edgecolor="black",
linewidth=1.2,
width=0.6
)
plt.title("Pathologic stage")
plt.xlabel("Stage")
plt.ylabel("Number of patients")
plt.tight_layout()
plt.show()
Cancer staging often depends partly on whether cancer has spread to lymph nodes.
The column number_of_lymphnodes_positive looks numeric,
but because it contains values such as [Not Available],
Python may read it as text.
We can convert it carefully.
lymph_nodes_positive = pd.to_numeric(
brca_clin_df["number_of_lymphnodes_positive"],
errors="coerce"
)
lymph_nodes_positive.describe()
## count 919.000000
## mean 2.381937
## std 4.654389
## min 0.000000
## 25% 0.000000
## 50% 1.000000
## 75% 2.000000
## max 35.000000
## Name: number_of_lymphnodes_positive, dtype: float64
The option errors="coerce" tells pandas to turn
non-numeric values like [Not Available] into missing
values.
Now we can compare positive lymph node counts by pathologic stage. # Fixed the boxplot (made the figure bigger to be able to see distribution)
boxplot_df = pd.DataFrame({
"pathologic_stage": brca_clin_df["pathologic_stage"],
"lymph_nodes_positive": lymph_nodes_positive
}).dropna()
stage_order = sorted(boxplot_df["pathologic_stage"].unique())
boxplot_data = [
boxplot_df.loc[boxplot_df["pathologic_stage"] == stage, "lymph_nodes_positive"]
for stage in stage_order
]
plt.figure(figsize=(9, 4))
plt.boxplot(boxplot_data, labels=stage_order)
## {'whiskers': [<matplotlib.lines.Line2D object at 0x78985406c710>, <matplotlib.lines.Line2D object at 0x78985406ca10>, <matplotlib.lines.Line2D object at 0x78985406da60>, <matplotlib.lines.Line2D object at 0x78985406dd90>, <matplotlib.lines.Line2D object at 0x78985406edb0>, <matplotlib.lines.Line2D object at 0x78985406f0b0>, <matplotlib.lines.Line2D object at 0x789853e9c200>, <matplotlib.lines.Line2D object at 0x789853e9c4d0>, <matplotlib.lines.Line2D object at 0x789853e9d4f0>, <matplotlib.lines.Line2D object at 0x789853e9d760>, <matplotlib.lines.Line2D object at 0x789853e9e720>, <matplotlib.lines.Line2D object at 0x789853e9ea20>], 'caps': [<matplotlib.lines.Line2D object at 0x78985406ccb0>, <matplotlib.lines.Line2D object at 0x78985406cf80>, <matplotlib.lines.Line2D object at 0x78985406e060>, <matplotlib.lines.Line2D object at 0x78985406e300>, <matplotlib.lines.Line2D object at 0x78985406f350>, <matplotlib.lines.Line2D object at 0x78985406f650>, <matplotlib.lines.Line2D object at 0x789853e9c7a0>, <matplotlib.lines.Line2D object at 0x789853e9c9e0>, <matplotlib.lines.Line2D object at 0x789853e9da60>, <matplotlib.lines.Line2D object at 0x789853e9dd00>, <matplotlib.lines.Line2D object at 0x789853e9ecf0>, <matplotlib.lines.Line2D object at 0x789853e9ef60>], 'boxes': [<matplotlib.lines.Line2D object at 0x789853faa510>, <matplotlib.lines.Line2D object at 0x78985406d7c0>, <matplotlib.lines.Line2D object at 0x78985406eb10>, <matplotlib.lines.Line2D object at 0x78985406fef0>, <matplotlib.lines.Line2D object at 0x789853e9d1f0>, <matplotlib.lines.Line2D object at 0x789853e9e4b0>], 'medians': [<matplotlib.lines.Line2D object at 0x78985406d250>, <matplotlib.lines.Line2D object at 0x78985406e5d0>, <matplotlib.lines.Line2D object at 0x78985406f920>, <matplotlib.lines.Line2D object at 0x789853e9ccb0>, <matplotlib.lines.Line2D object at 0x789853e9dfd0>, <matplotlib.lines.Line2D object at 0x789853e9f260>], 'fliers': [<matplotlib.lines.Line2D object at 0x78985406d580>, <matplotlib.lines.Line2D object at 0x78985406e870>, <matplotlib.lines.Line2D object at 0x78985406fc20>, <matplotlib.lines.Line2D object at 0x789853e9cfb0>, <matplotlib.lines.Line2D object at 0x789853e9e270>, <matplotlib.lines.Line2D object at 0x789853e9f470>], 'means': []}
plt.title("Positive lymph nodes by pathologic stage")
plt.xlabel("Pathologic stage")
plt.ylabel("Number of positive lymph nodes")
plt.tight_layout()
plt.show()
Question: What pattern do you see?
Your answer: The pattern is the number of positive lymph nodes increases as the cancer stage becomes more in a advance state.
In this activity, you used Python to:
Most importantly, you saw that real biomedical data are both powerful and messy.
That is normal. A major part of computational biology is learning how to reason carefully with complex, imperfect data.
Save your file. Then click:
Knit → Knit to HTML
Your final HTML report should include:
This is one reason R Markdown is powerful: it combines writing, code, results, and figures in one reproducible document.
In later DREAM-High activities, we will connect these clinical features to gene expression data from the same cancer type.
That will allow us to ask a deeper systems biology question:
Can molecular data reveal patterns that help us understand cancer subtype, treatment, and disease behavior?