Project conducted by R.P.M. Kras¶

Personal development project¶

Source: https://www.kaggle.com/datasets/bhargavchirumamilla/thyroid-cancer-risk-dataset

Used in accordance to the Attribution 4.0 International license.

Libraries¶

In [ ]:
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from sklearn import svm
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
In [2]:
data = pd.read_csv('thyroid_cancer_risk_data.csv')
data.head()
Out[2]:
Patient_ID Age Gender Country Ethnicity Family_History Radiation_Exposure Iodine_Deficiency Smoking Obesity Diabetes TSH_Level T3_Level T4_Level Nodule_Size Thyroid_Cancer_Risk Diagnosis
0 1 66 Male Russia Caucasian No Yes No No No No 9.37 1.67 6.16 1.08 Low Benign
1 2 29 Male Germany Hispanic No Yes No No No No 1.83 1.73 10.54 4.05 Low Benign
2 3 86 Male Nigeria Caucasian No No No No No No 6.26 2.59 10.57 4.61 Low Benign
3 4 75 Female India Asian No No No No No No 4.10 2.62 11.04 2.46 Medium Benign
4 5 35 Female Germany African Yes Yes No No No No 9.10 2.11 10.71 2.11 High Benign

The dataset contains zero missing values! Good Kaggle datasets are so convenient.

In [3]:
data.isna().sum()
Out[3]:
Patient_ID             0
Age                    0
Gender                 0
Country                0
Ethnicity              0
Family_History         0
Radiation_Exposure     0
Iodine_Deficiency      0
Smoking                0
Obesity                0
Diabetes               0
TSH_Level              0
T3_Level               0
T4_Level               0
Nodule_Size            0
Thyroid_Cancer_Risk    0
Diagnosis              0
dtype: int64
In [4]:
data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 212691 entries, 0 to 212690
Data columns (total 17 columns):
 #   Column               Non-Null Count   Dtype  
---  ------               --------------   -----  
 0   Patient_ID           212691 non-null  int64  
 1   Age                  212691 non-null  int64  
 2   Gender               212691 non-null  object 
 3   Country              212691 non-null  object 
 4   Ethnicity            212691 non-null  object 
 5   Family_History       212691 non-null  object 
 6   Radiation_Exposure   212691 non-null  object 
 7   Iodine_Deficiency    212691 non-null  object 
 8   Smoking              212691 non-null  object 
 9   Obesity              212691 non-null  object 
 10  Diabetes             212691 non-null  object 
 11  TSH_Level            212691 non-null  float64
 12  T3_Level             212691 non-null  float64
 13  T4_Level             212691 non-null  float64
 14  Nodule_Size          212691 non-null  float64
 15  Thyroid_Cancer_Risk  212691 non-null  object 
 16  Diagnosis            212691 non-null  object 
dtypes: float64(4), int64(2), object(11)
memory usage: 27.6+ MB
In [5]:
data['Diagnosis'].nunique()
Out[5]:
2
In [6]:
data['Diagnosis']
Out[6]:
0            Benign
1            Benign
2            Benign
3            Benign
4            Benign
            ...    
212686       Benign
212687       Benign
212688       Benign
212689       Benign
212690    Malignant
Name: Diagnosis, Length: 212691, dtype: object
In [7]:
data.dtypes.value_counts()
Out[7]:
object     11
float64     4
int64       2
Name: count, dtype: int64

Data Preparation¶

This section includes the required label encoding, primarily.

In [8]:
le = LabelEncoder()

categorical_cols = data.select_dtypes(include=['object']).columns

for col in categorical_cols:
    data[col] = le.fit_transform(data[col])

data.head()
Out[8]:
Patient_ID Age Gender Country Ethnicity Family_History Radiation_Exposure Iodine_Deficiency Smoking Obesity Diabetes TSH_Level T3_Level T4_Level Nodule_Size Thyroid_Cancer_Risk Diagnosis
0 1 66 1 6 2 0 1 0 0 0 0 9.37 1.67 6.16 1.08 1 0
1 2 29 1 2 3 0 1 0 0 0 0 1.83 1.73 10.54 4.05 1 0
2 3 86 1 5 2 0 0 0 0 0 0 6.26 2.59 10.57 4.61 1 0
3 4 75 0 3 1 0 0 0 0 0 0 4.10 2.62 11.04 2.46 2 0
4 5 35 0 2 0 1 1 0 0 0 0 9.10 2.11 10.71 2.11 0 0

After encoding all categorical labels, we can now analyse the difference between the quantity of features between classes.

Data analysis¶

From plotting the recorded ages, we can tell that overall the dataset is relatively balanced.

In [9]:
print("Youngest recorded instance: ", data['Age'].min())
print("Oldest age recorded: ", data['Age'].max())
print("Unique number of ages: ", data['Age'].nunique())

data.plot.hist(y="Age", bins=75)
Youngest recorded instance:  15
Oldest age recorded:  89
Unique number of ages:  75
Out[9]:
<Axes: ylabel='Frequency'>
No description has been provided for this image
In [ ]:
columns = ["Smoking", "Obesity", "Diabetes"]

fig, axes = plt.subplots(1, len(columns), figsize=(15, 5))

for ax, col in zip(axes, columns):
    counts = data[col].value_counts() 
    ax.pie(counts, labels=counts.index, autopct='%1.1f%%', startangle=90, colors=plt.cm.Paired.colors)
    ax.set_title(col)

plt.tight_layout()
plt.show()
No description has been provided for this image

Training the model¶

In [25]:
X = data.drop('Diagnosis', axis=1)
y = data['Diagnosis']

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state=42)
In [32]:
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
from sklearn.metrics import accuracy_score, classification_report
from sklearn.model_selection import GridSearchCV

Logistic Regression¶

In [43]:
log_reg = linear_model.LogisticRegression(solver='lbfgs', random_state=42, max_iter=10000)

log_reg.fit(X_train, y_train)
y_pred_log = log_reg.predict(X_test)

print(classification_report(y_test, y_pred_log))
              precision    recall  f1-score   support

           0       0.84      0.94      0.89     32615
           1       0.69      0.42      0.52      9924

    accuracy                           0.82     42539
   macro avg       0.77      0.68      0.71     42539
weighted avg       0.81      0.82      0.80     42539

Now for some max iterations finetuning:

In [44]:
parameters = [
    {"max_iter":[100, 1000, 10000]}
]

clf = GridSearchCV(LogisticRegression(solver='lbfgs', random_state=42), param_grid=parameters, cv=5, scoring="recall")

clf.fit(X_train, y_train)
clf_y_pred = clf.predict(X_test)

print(classification_report(y_test, clf_y_pred))
c:\Users\robkr\AppData\Local\Programs\Python\Python311\Lib\site-packages\sklearn\linear_model\_logistic.py:469: ConvergenceWarning: lbfgs failed to converge (status=1):
STOP: TOTAL NO. of ITERATIONS REACHED LIMIT.

Increase the number of iterations (max_iter) or scale the data as shown in:
    https://scikit-learn.org/stable/modules/preprocessing.html
Please also refer to the documentation for alternative solver options:
    https://scikit-learn.org/stable/modules/linear_model.html#logistic-regression
  n_iter_i = _check_optimize_result(
c:\Users\robkr\AppData\Local\Programs\Python\Python311\Lib\site-packages\sklearn\linear_model\_logistic.py:469: ConvergenceWarning: lbfgs failed to converge (status=1):
STOP: TOTAL NO. of ITERATIONS REACHED LIMIT.

Increase the number of iterations (max_iter) or scale the data as shown in:
    https://scikit-learn.org/stable/modules/preprocessing.html
Please also refer to the documentation for alternative solver options:
    https://scikit-learn.org/stable/modules/linear_model.html#logistic-regression
  n_iter_i = _check_optimize_result(
c:\Users\robkr\AppData\Local\Programs\Python\Python311\Lib\site-packages\sklearn\linear_model\_logistic.py:469: ConvergenceWarning: lbfgs failed to converge (status=1):
STOP: TOTAL NO. of ITERATIONS REACHED LIMIT.

Increase the number of iterations (max_iter) or scale the data as shown in:
    https://scikit-learn.org/stable/modules/preprocessing.html
Please also refer to the documentation for alternative solver options:
    https://scikit-learn.org/stable/modules/linear_model.html#logistic-regression
  n_iter_i = _check_optimize_result(
c:\Users\robkr\AppData\Local\Programs\Python\Python311\Lib\site-packages\sklearn\linear_model\_logistic.py:469: ConvergenceWarning: lbfgs failed to converge (status=1):
STOP: TOTAL NO. of ITERATIONS REACHED LIMIT.

Increase the number of iterations (max_iter) or scale the data as shown in:
    https://scikit-learn.org/stable/modules/preprocessing.html
Please also refer to the documentation for alternative solver options:
    https://scikit-learn.org/stable/modules/linear_model.html#logistic-regression
  n_iter_i = _check_optimize_result(
c:\Users\robkr\AppData\Local\Programs\Python\Python311\Lib\site-packages\sklearn\linear_model\_logistic.py:469: ConvergenceWarning: lbfgs failed to converge (status=1):
STOP: TOTAL NO. of ITERATIONS REACHED LIMIT.

Increase the number of iterations (max_iter) or scale the data as shown in:
    https://scikit-learn.org/stable/modules/preprocessing.html
Please also refer to the documentation for alternative solver options:
    https://scikit-learn.org/stable/modules/linear_model.html#logistic-regression
  n_iter_i = _check_optimize_result(
c:\Users\robkr\AppData\Local\Programs\Python\Python311\Lib\site-packages\sklearn\linear_model\_logistic.py:469: ConvergenceWarning: lbfgs failed to converge (status=1):
STOP: TOTAL NO. of ITERATIONS REACHED LIMIT.

Increase the number of iterations (max_iter) or scale the data as shown in:
    https://scikit-learn.org/stable/modules/preprocessing.html
Please also refer to the documentation for alternative solver options:
    https://scikit-learn.org/stable/modules/linear_model.html#logistic-regression
  n_iter_i = _check_optimize_result(
c:\Users\robkr\AppData\Local\Programs\Python\Python311\Lib\site-packages\sklearn\linear_model\_logistic.py:469: ConvergenceWarning: lbfgs failed to converge (status=1):
STOP: TOTAL NO. of ITERATIONS REACHED LIMIT.

Increase the number of iterations (max_iter) or scale the data as shown in:
    https://scikit-learn.org/stable/modules/preprocessing.html
Please also refer to the documentation for alternative solver options:
    https://scikit-learn.org/stable/modules/linear_model.html#logistic-regression
  n_iter_i = _check_optimize_result(
c:\Users\robkr\AppData\Local\Programs\Python\Python311\Lib\site-packages\sklearn\linear_model\_logistic.py:469: ConvergenceWarning: lbfgs failed to converge (status=1):
STOP: TOTAL NO. of ITERATIONS REACHED LIMIT.

Increase the number of iterations (max_iter) or scale the data as shown in:
    https://scikit-learn.org/stable/modules/preprocessing.html
Please also refer to the documentation for alternative solver options:
    https://scikit-learn.org/stable/modules/linear_model.html#logistic-regression
  n_iter_i = _check_optimize_result(
c:\Users\robkr\AppData\Local\Programs\Python\Python311\Lib\site-packages\sklearn\linear_model\_logistic.py:469: ConvergenceWarning: lbfgs failed to converge (status=1):
STOP: TOTAL NO. of ITERATIONS REACHED LIMIT.

Increase the number of iterations (max_iter) or scale the data as shown in:
    https://scikit-learn.org/stable/modules/preprocessing.html
Please also refer to the documentation for alternative solver options:
    https://scikit-learn.org/stable/modules/linear_model.html#logistic-regression
  n_iter_i = _check_optimize_result(
              precision    recall  f1-score   support

           0       0.84      0.94      0.89     32615
           1       0.69      0.42      0.53      9924

    accuracy                           0.82     42539
   macro avg       0.77      0.68      0.71     42539
weighted avg       0.81      0.82      0.81     42539

c:\Users\robkr\AppData\Local\Programs\Python\Python311\Lib\site-packages\sklearn\linear_model\_logistic.py:469: ConvergenceWarning: lbfgs failed to converge (status=1):
STOP: TOTAL NO. of ITERATIONS REACHED LIMIT.

Increase the number of iterations (max_iter) or scale the data as shown in:
    https://scikit-learn.org/stable/modules/preprocessing.html
Please also refer to the documentation for alternative solver options:
    https://scikit-learn.org/stable/modules/linear_model.html#logistic-regression
  n_iter_i = _check_optimize_result(
In [45]:
from sklearn.metrics import confusion_matrix
cm = confusion_matrix(y_test, clf_y_pred)

plt.figure(figsize=(8, 6))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', cbar=False)
plt.xlabel('Predicted')
plt.ylabel('True')
plt.title('Confusion Matrix')
plt.show()
No description has been provided for this image

Random Forest¶

In [46]:
ran_for = RandomForestClassifier(n_estimators=100, random_state=42)

ran_for.fit(X_train, y_train)
y_pred_rf = ran_for.predict(X_test)

print(classification_report(y_test, y_pred_log))
              precision    recall  f1-score   support

           0       0.84      0.94      0.89     32615
           1       0.69      0.42      0.52      9924

    accuracy                           0.82     42539
   macro avg       0.77      0.68      0.71     42539
weighted avg       0.81      0.82      0.80     42539

In [47]:
from sklearn.metrics import confusion_matrix
cm = confusion_matrix(y_test, y_pred_rf)

plt.figure(figsize=(8, 6))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', cbar=False)
plt.xlabel('Predicted')
plt.ylabel('True')
plt.title('Confusion Matrix')
plt.show()
No description has been provided for this image

Conclusion¶

The results suggest that the ability of both models to correctly classify someone as having heart disease is rather low. This is supported by the relatively low recall score for patients who were diagnosed with the disease.

This was just a for-fun project for me to learn and grow more during my free time, albeit it being very simple and not having enough finetuning.