Title: What is the correct order in data preprocessing stage for Machine Learning?
Tags: python,machine-learning,scikit-learn,pipeline,data-preprocessing

Question: I am trying to create some sort of step-by-step guide/cheat sheet for myself on how to correctly go over the data preprocessing stage for Machine Learning.
Let's imagine we have a binary Classification problem.
Would the below strategy work or do I have to change/modify the order of some of the steps and maybe something should be added or removed?
1. LOAD DATA
import pandas as pd    

df = pd.read_csv("data.csv")

2. SPLIT DATA - I understand, that to prevent "data leakage", we MUST split data into training (work with it) and testing (pretend it does not exist) sets.
from sklearn.model_selection import train_test_split

# stratify = 'target' if proportion disbalance in data, so training and testing sets will have the same proportion after splitting.
train_df, test_df = train_test_split(df, test_size = 0.33, random_state = 42, stratify = 'target')    

3. EDA ON TRAINING DATA - Is it correct to look at the training set only or should we do EDA before splitting? If we assume the Test set doesn't exist, then we should not care what is there, right?
train_df.info()
train_df.describe()
# + Plots etc.

4. OUTLIERS ON TRAINING DATA - If we have to scale the data, the Mean (Average) is very sensitive to outliers, therefore we have to take care of them in the beginning. Also, if we decide to fill Null numerical features with mean, outliers may be a problem in this case.
import matplotlib.pyplot as plt
import seaborn as sns 

# Check distributions
sns.diplot(train_df)    
sns.boxplot(train_df)   
train_df.corr()    # Correlation between all features and label
train_df.corr()["target"].sort_values()
sns.scatterplot(x = "Column X", y = 'target', data = train_df)

train_df.describe() # above 75% + 1.5 * (75% - 25%) and below 25% - 1.5 * (75% - 25%)

5. MISSING VALUES ON TRAINING DATA - We can't have Null values. We either remove or fill in them. This step should be taken care of in the beginning.
train_df.info()
train_df.isnull().sum() # or train_df.isna().sum()
# Show the rows with Null values
train_df[train_df["Column"].isnull()]      

6. FEATURE ENGINEERING ON TRAINING DATA - Is this step should be taken care of in the beginning as well? I think so because we can create the feature that might need to be scaled.
# If some columns (not target) correlated with each other, we should delete one of them, or make some sort of blending.
train_df.corr()
train_df = train_df.drop("1 of Correlated X Column", axis = 1)

# For normally distributed data, the skewness should be about 0. A skewness value > 0 means there is more weight in the left tail of the distribution
# We should try to have normal distribution in the columns
train_df["Not Skewed Column"] = np.log(train_df["Skewed Column"] + 1)
train_df["Not Skewed Column"].hist(figsize = (20,5))
plt.show()

7. CATEGORICAL DATA - We can't have objects in the data frame.
from sklearn.preprocessing import OneHotEncoder     # Just an example

# Create X and y variables
X_train = train_df.drop('target', axis = 1)
y_train = np.where(train['target'] == 'yes', 1, 0)

# Create the one hot encoder
onehot = OneHotEncoder(handle_unknown = 'ignore')

# Apply one hot encoding to categorical columns 
encoded_columns = onehot.fit_transform(X_train.select_dtypes(include = 'object')).toarray()

X_train = X_train.select_dtypes(exclude = 'object')
X_train[onehot.get_feature_names_out()] = encoded_columns

8. IMBALANCED DATA - Good to have the same or similar number of observations in the target column.
 from imblearn.over_sampling import SMOTE      # Just an example

 # Create the SMOTE class
 sm = SMOTE(random_state = 42)

 # Resample to balance the dataset
 X_train, y_train = sm.fit_resample(X_train, y_train)

9. SCALE DATA - Should we scale the target column in the Regression task?
# Brings mean close to 0 and std to 1. Formula = (x - mean) / std
from sklearn.preprocessing import StandardScaler      # Just an example

scaler = StandardScaler()
scaled_X_train = scaler.fit_transform(X_train)    # X_test we don't fit, only transform!

10. PRINCIPAL COMPONENT ANALYSIS (PCA) - REDUCING DIMENSIONALITY - Should data be scaled before applying PCA?
# Example: PCA = 50 (n_components). Let's say Input is 100 X features, after applying PCA, Output will be 50 X features.
# Why don't use PCA all the time? We lose the ability to explain what each value is because they are now in combination with a whole bunch of features. 
# Will not be able to look at feature importance, trees, etc. We use it when we need to. 
# If we are able to train the model with all features, then great. if can't, we can apply PCA, but be ready to lose the ability to explain what is driving the machine learning model.

from sklearn.decomposition import PCA     # Just an example

pca = PCA(n_components = 50)  # Just an Example
scaled_X_train = pca.fit_transform(scaled_X_train)    # X_test we don't fit, only transform!

11. MODEL, FIT, EVALUATE, PREDICT
from sklearn.linear_model import RidgeClassifier          # Just an Example  
from sklearn.metrics import accuracy_score, recall_score, precision_score, f1_score, confusion_matrix

model = RidgeClassifier()
model.fit(scaled_X_train, y_train)

# HERE we should create and / or execute transformation function that will take test_df as input and will return scaled_X_test and y_test

y_pred = model.predict(scaled_X_test)

# Evaluate model - Calculate Classification metrics
accuracy = accuracy_score(y_test, y_pred)
precision = precision_score(y_test, y_pred)
recall = recall_score(y_test, y_pred)
f1 = f1_score(y_test, y_pred)
print(f"RidgeClassifier model scores Accuracy: {accuracy}, Precision: {precision}, Recall: {recall}, F1-Score: {f1}")

confusion_matrix(y_test, y_pred, labels = [1,0])

12. SAVE MODEL
import joblib       # Just an example

# Save Model
joblib.dump(model, 'best_model.joblib')

Answer: I would suggest, the following steps -

EDA(Learn about data)
Finding correlations
Removing unnecessary features.
Working on preprocessing the data(Such as Outlier removal, Encoding Data)
Split features and target variables(X and Y)
Train Test Split
Perform scaling(Scaling before train test split will lead to data leakage)
Choose the algorithm, depending on the usecase (TreeBased models doesn't get effected by outliers and different scale of data so you can reduce those steps while selecting these models)
Depending on the usecase select the metrics to judge your model's performance.(Confusion matrix, f1 score, precision, recall, rmse, mse)