<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dare Johnson</title>
    <description>The latest articles on DEV Community by Dare Johnson (@dare_johnson).</description>
    <link>https://dev.to/dare_johnson</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1138243%2F2a60ecff-b36f-4d85-a401-70b3a2092736.png</url>
      <title>DEV Community: Dare Johnson</title>
      <link>https://dev.to/dare_johnson</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dare_johnson"/>
    <language>en</language>
    <item>
      <title>How To Implement Code Modularity in Data Science and Machine Learning Projects</title>
      <dc:creator>Dare Johnson</dc:creator>
      <pubDate>Sat, 10 Feb 2024 08:23:36 +0000</pubDate>
      <link>https://dev.to/dare_johnson/how-to-implement-code-modularity-in-data-science-and-machine-learning-projects-1fgm</link>
      <guid>https://dev.to/dare_johnson/how-to-implement-code-modularity-in-data-science-and-machine-learning-projects-1fgm</guid>
      <description>&lt;p&gt;In the data science field where things evolve fast and codes can soon become obsolete, the ability to know how to structure projects can not be overstated.  As data projects become complex, adopting a modular code approach emerges as a strategic imperative. Unfortunately, many new learners in the field of data science aren’t taught this modular approach to structuring data science scripts. The reason partly being that many data boot camps or data schools don’t include an end-to-end approach or method, which uses modularization of different stages as the industry standards.&lt;/p&gt;

&lt;p&gt;This guide examines the substantial advantages of structuring data science projects using modular code approaches, highlights the inherent limitations of conventional tools such as Jupyter Notebooks, presents a recommended project structure, and provides a comprehensive demonstration utilizing the iconic Iris dataset.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Outline
&lt;/h3&gt;

&lt;p&gt;Code Modularity&lt;br&gt;
Advantages of Modular Code in Data Science Projects&lt;br&gt;
Limitations of Jupyter Notebook&lt;br&gt;
Common Machine Learning Lifecycle&lt;br&gt;
Demonstration Using The Iris Dataset&lt;br&gt;
Conclusion&lt;br&gt;
&lt;br&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Code Modularity&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;The practice of breaking down a program into separate components (more appropriate, modules, in programming parlance) whereby each component or module is responsible for a specific functionality is known as code modularity. In essence, when applied to a data science problem, code modularity is all about breaking down different stages of a data project into separate modules or scripts.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Advantages of Modular Code&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Code Organization and Readability&lt;/strong&gt;&lt;br&gt;
The strategic compartmentalization of code into modules fosters a sense of clarity, disentangling intricate project structures. This approach heightens the readability of the codebase, enabling a more lucid understanding of individual components.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Code Reusability&lt;/strong&gt;&lt;br&gt;
Modules serve as reusable building blocks that transcend project boundaries. Thoughtfully designed modules can be repurposed, eliminating redundancy and expediting the development lifecycle.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Collaboration and Teamwork&lt;/strong&gt;&lt;br&gt;
A modular structure paves the way for seamless collaboration among team members. Each module can be independently developed, tested, and maintained, allowing concurrent progress on different aspects of the project.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Maintainability and Debugging&lt;/strong&gt;&lt;br&gt;
Debugging becomes a more straightforward endeavor as issues are confined within specific modules. Changes made to one module can be contained and tested, reducing unintended side effects throughout the codebase.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Limitations of Jupyter Notebook&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Generally, data scientists love the Jupyter Notebook, myself also included. Jupyter Notebook arguably birthed the inspiration behind &lt;a href="https://colab.research.google.com/" rel="noopener noreferrer"&gt;Google Colab&lt;/a&gt; and &lt;a href="https://deepnote.com/" rel="noopener noreferrer"&gt;Deepnote&lt;/a&gt;. The Jupyter Notebook is so popular that several IDEs (VS Code, JetBrains DataSpell, etc.) support it.&lt;/p&gt;

&lt;p&gt;While the Jupyter Notebook offers interactivity and is widely accepted and adopted, it carries certain limitations that hinder efficient project organization, among which are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Monolithic Structure&lt;/strong&gt;&lt;br&gt;
This is best explained as a situation where codes from data ingestion all through to the stage of model deployment (or some other steps in the data science process) are in one codebase, for instance in a single Jupyter Notebook.&lt;br&gt;&lt;br&gt;
One major disadvantage is that it can make the code more difficult to maintain and debug. Since all of the code is in a single notebook, it can be challenging to find and fix errors or to update the code without affecting other parts of the notebook&lt;br&gt;
&lt;br&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Version Control and Collaboration&lt;/strong&gt;&lt;br&gt;
Steps of different stages in data science projects are repetitive and iterative, thus it becomes very necessary to have a version control system in place to keep track of changes over time, and when necessary to revert to previous versions if something goes wrong. While version control systems such as Git can also track changes to the code inside a Jupyter Notebook, it can be more challenging to track changes to the codebase. In addition, separating codes into modules not only encourages easy maintainability and reuse, it also fosters collaboration, because different engineers can work on different versions and modules concurrently.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Therefore, at best, Jupyter Notebooks are most useful for Exploratory Data analysis (EDA) and prototyping.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Typical Stages of Machine Learning Project&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Depending on the complexity of a typical machine learning project, most data science/ML projects have some or more of these structures: &lt;strong&gt;data ingestion and loading&lt;/strong&gt;, &lt;strong&gt;data preprocessing&lt;/strong&gt;, &lt;strong&gt;data exploration&lt;/strong&gt;, &lt;strong&gt;feature engineering and feature selection&lt;/strong&gt;, &lt;strong&gt;model training and model evaluation&lt;/strong&gt;, &lt;strong&gt;model deployment&lt;/strong&gt;, etc. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Data Ingestion and loading&lt;/strong&gt;: The process of importing data from one or more sources to a targeted location, either for storage or immediate use. Common data sources include APIs, databases/ data warehouses, etc. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Data Preprocessing&lt;/strong&gt;: A critical step in every data science project that involves cleaning and transforming data into useful formats suitable for analysis&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Data Exploration and Visualization&lt;/strong&gt;: The process of examining data to understand it by summarizing it, and identifying patterns and relationships, sometimes with the aid of visualizations. It is a critical step that reveals potential concerns such as missing values, outliers, etc.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Feature Engineering and Selection&lt;/strong&gt;: While feature engineering involves crafting or transforming more features or attributes (e.g. log transform, binning, etc.) intending to improve the performance of machine learning models, feature selection, on the other hand, is aimed at identifying and removing irrelevant and redundant features which do not contribute to the model accuracy, based on some certain rules.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Model Training and Evaluation&lt;/strong&gt;: model training includes making the machine learning algorithm learn patterns from the training data to make predictions, while (model) evaluation is the process of accessing the performance of a model on test data using a particular evaluation setup. The goal of the former is to minimize errors between predictions and the actual outcomes, while the latter aims to measure (evaluate) the performance of the model on unseen data.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Model Deployment&lt;/strong&gt;: This involves the process of making a trained model available for end users in production, to use for prediction on new data.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Demonstration Using The Iris Dataset&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;To elucidate the potency of modularity, let's apply this approach to the popular Iris dataset.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Load data&lt;/strong&gt;&lt;br&gt;
We will leverage Scikit-learn's built-in functionality to effortlessly load the Iris dataset. The Iris dataset version is tied to the version of Scikit-learn presently installed on our machine. The Scikit-learn version used in this project is version 1.3.0. To know your own Scikit-learn version, you can execute the code here in either of Python terminal or Jupyter notebook &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

import sklearn
print(sklearn.__version__)


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;To load the dataset, use the code snippet below and save the code as data_loading.py in a directory. In this same directory, we would save the rest of the codes further down this article. &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

# data_loading.py
from sklearn.datasets import load_iris

# create the function to load the data
def load_data():
    iris = load_iris()
    data = iris.data
    target = iris.target
    return data, target


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;To see the output, add and execute the following code as the latter end of the data_loading.py.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

data, target = load_data()
print("Data:")
print(data)
print(“Target:")
print(target)


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;To run data_loading.py, open your command line or terminal, navigate to the directory where you saved script.py (in our case data_loading.py), and then type “python data_loading.py”, after running it, the output should look as below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmnmvwqrmzsg1mwq8h4ag.JPG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmnmvwqrmzsg1mwq8h4ag.JPG" alt="Data"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feip7z6bd7i6bqrmxbd0a.JPG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feip7z6bd7i6bqrmxbd0a.JPG" alt="Target"&gt;&lt;/a&gt;&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Data preprocessing&lt;/strong&gt;&lt;br&gt;
The only preprocess step here is to split our data into train and test data, and then we save our data as data_preprocessing.py.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

# data_preprocessing.py
from data_loading import load_data # to access the data and target from data_loading.py file
from sklearn.model_selection import train_test_split

def preprocess_data(data, target):
    X_train, X_test, y_train, y_test = train_test_split(data, target, test_size=0.2, random_state=42)
    return X_train, X_test, y_train, y_test


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;To see the output for the X_train, X_test, y_train and y_test respectively, running the code below as part of the data_preprocess.py will print out X_train, X_test, y_train and y_test:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

data, target = load_data()
X_train, X_test, y_train, y_test = preprocess_data(data, target)
print("X_train:")
print(X_train)
print("X_test:")
print(X_test)
print("y_train:")
print(y_train)
print("y_test:")
print(y_test)


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Output for y_train and y_test is as below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnvyvxn91dd2ucdwf0854.JPG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnvyvxn91dd2ucdwf0854.JPG" alt="y_train y_test"&gt;&lt;/a&gt;&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Feature engineering/selection&lt;/strong&gt; &lt;br&gt;
These modules/stages can be extended based on specific project requirements. Our demo data (Iris dataset) is small in our case, and these stages might not be necessary.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Model training&lt;/strong&gt;&lt;br&gt;
We will train a Random Forest Classifier on the Iris dataset to create and save as model_training.py in the same directory as before.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

# model_training.py
from data_loading import load_data # to access the data and target from data_loading.py file
from data_preprocessing import preprocess_data # to access the function from data_preprocessing.py file
from sklearn.ensemble import RandomForestClassifier 

# create a function to fit the algorithm to learn from the data
def train_model(X_train, y_train):
    model = RandomForestClassifier(random_state=42)
    model.fit(X_train, y_train)
    return model

data, target = load_data()
X_train, X_test, y_train, y_test = preprocess_data(data, target)
model = train_model(X_train, y_train)

print(model)


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The output here is nothing but the actual model, i.e &lt;/p&gt;

&lt;p&gt;&lt;code&gt;RandomForestClassifier(random_state=42)&lt;/code&gt;&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Model evaluation&lt;/strong&gt;&lt;br&gt;
In this, we will evaluate our model and save the file as model_evaluation.py&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

# model_evaluation.py
from data_loading import load_data # from data_loading.py file
from data_preprocessing import preprocess_data # from data_preprocessing.py file
from model_training import train_model # from model_training.py file
from sklearn.metrics import accuracy_score

def evaluate_model(model, X_test, y_test):
    y_pred = model.predict(X_test)
    accuracy = accuracy_score(y_test, y_pred)
    return accuracy


data, target = load_data()
X_train, X_test, y_train, y_test = preprocess_data(data, target)
model = train_model(X_train, y_train)
accuracy = evaluate_model(model, X_test, y_test)
print(f"The accuracy of the model is {accuracy:.2f}")


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The output of our codes above would display the accuracy of the model as 1.00.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2yklyq10twm14kaayij4.JPG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2yklyq10twm14kaayij4.JPG" alt="Accuracy"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In a real and ideal situation, an accuracy of 100% (1.00) would call for a concern; this, most of the time would suggest “overfitting”: a situation where the model has learned the training data so well, and thus not performing well on the new data. However in our case, the simplicity (small and not messy) of our data here is for a mere demonstrative purpose for code modularity, and not the subject of overfitting and accuracy.&lt;/p&gt;

&lt;p&gt;Having said that, let’s consider how the hyperparameter code would look like even though, our accuracy has already churned out a perfect metric&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Hyperparameter tuning&lt;/strong&gt;&lt;br&gt;
We will fine-tune the model's hyperparameters using a GridSearchCV approach and save it as hyperparameter_tuning.py with the code below:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

# hyperparameter_tuning.py
from data_loading import load_data # from data_loading.py file
from data_preprocessing import preprocess_data # from data_preprocessing.py file
from model_training import train_model # from model_training.py file

from sklearn.model_selection import GridSearchCV
from sklearn.ensemble import RandomForestClassifier 


def tune_hyperparameters(X_train, y_train):
    param_grid = {
        # tuning just 3:
        'n_estimators': [50, 100, 200],
        'max_depth': [None, 10, 20],
        'min_samples_split': [2, 5, 10]
    }

    grid_search = GridSearchCV(RandomForestClassifier(random_state=42),
                               param_grid, cv=3, verbose=1)
    grid_search.fit(X_train, y_train)
    return grid_search.best_params_

data, target = load_data()
X_train, X_test, y_train, y_test = preprocess_data(data, target)
best_params = tune_hyperparameters(X_train, y_train)
print(f"The best hyperparameters are {best_params}")



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The output of hyperparameter tuning:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhckqig847qr0wtyj0y0s.JPG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhckqig847qr0wtyj0y0s.JPG" alt="Hyperparameter"&gt;&lt;/a&gt;&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. The main script&lt;/strong&gt;&lt;br&gt;
Now, having had the separate files (.py) for all the stages or steps of building our model, the main.py script (below) orchestrates the entire workflow from data ingestion to hyperparameter tuning using the modular components:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

# main.py
import data_loading
import data_preprocessing
import hyperparameter_tuning
import model_training
import model_evaluation

if __name__ == "__main__":
    # Load Data
    data, target = data_loading.load_data()

    # Preprocess Data
    X_train, X_test, y_train, y_test = data_preprocessing.preprocess_data(data, target)

       # Hyperparameter Tuning
    best_params = hyperparameter_tuning.tune_hyperparameters(X_train, y_train)
    print("Best hyperparameters:", best_params)

    # Train Model
    model = model_training.train_model(X_train, y_train)

    # Evaluate Model
    accuracy = model_evaluation.evaluate_model(model, X_test, y_test)
    print("Model accuracy:", accuracy)


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The outputs will be the individual output of each of the separate code files, culminating in displaying the accuracy.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;In the expansive space of data science projects, embracing a modular code approach unleashes an array of benefits such as propelling towards a clean project organization, reusability, collaboration, and maintainability to new heights. The iris dataset used for illustration purposes might have been quite small and not messy; but notwithstanding, the idea is clear. Generally, as the code (.py or .r file) is methodically separated into well-defined modules, data scientists lay a solid foundation for efficient development and streamlined project management. Although Jupyter notebooks possess their own merits, they grapple with limitations about code organization, version control, and modularity.&lt;br&gt;
As a promising extension, there is a follow-up &lt;a href="https://dev.to/dare_johnson/how-to-streamline-machine-learning-projects-with-scikit-learn-pipeline-29ej"&gt;post&lt;/a&gt; to this, whereby there will be incorporation of the Scikit-learn Pipeline module, to further augment the project's modular development experience. The next article will examine the advantages of Pipelines, spotlighting the features of pipelines to automate and standardize data processing and modeling steps, thereby amplifying the development journey. &lt;a href="https://dev.to/dare_johnson/how-to-streamline-machine-learning-projects-with-scikit-learn-pipeline-29ej"&gt;Click&lt;/a&gt; to read.&lt;/p&gt;

</description>
      <category>datascience</category>
      <category>machinelearning</category>
      <category>scikitlearn</category>
      <category>python</category>
    </item>
    <item>
      <title>How to Streamline Machine Learning Projects with Scikit-learn Pipeline</title>
      <dc:creator>Dare Johnson</dc:creator>
      <pubDate>Sat, 10 Feb 2024 08:23:03 +0000</pubDate>
      <link>https://dev.to/dare_johnson/how-to-streamline-machine-learning-projects-with-scikit-learn-pipeline-29ej</link>
      <guid>https://dev.to/dare_johnson/how-to-streamline-machine-learning-projects-with-scikit-learn-pipeline-29ej</guid>
      <description>&lt;p&gt;This is a follow-up tutorial. We will go through how to use the Scikit Learn Pipeline module in addition to modularization. If you need to go through the previous tutorial which is on code modularization in data science, check &lt;a href="https://dev.to/dare_johnson/how-to-implement-code-modularity-in-data-science-and-machine-learning-projects-1fgm"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, just for a recap, the first tutorial introduced the industry standard of separating different stages of machine learning projects into modular scripts whereby each stage deals with different processes (data loading, data preprocessing, model selection, etc.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Table of Contents
&lt;/h3&gt;



&lt;ol&gt;
  &lt;li&gt;Scikit-learn Pipeline&lt;/li&gt;
  &lt;li&gt;Advantages of Scikit-learn Pipeline&lt;/li&gt;
  &lt;li&gt;
Illustration of Scikit-learn Pipeline
    &lt;ul&gt;
      &lt;li&gt;
About the dataset
&lt;/li&gt;
      &lt;li&gt;Step 1: Load the dataset
&lt;/li&gt;
      &lt;li&gt;Step 2: Preprocess Pipeline
&lt;/li&gt;
      &lt;li&gt;Step 3: Training Pipeline
&lt;/li&gt;
      &lt;li&gt;Step 4: Hyperparameter Pipeline
&lt;/li&gt;
      &lt;li&gt;
Serialize the model
&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The advantages of script modularization include code organization, code reusability, collaboration and teamwork, maintainability, and debugging.&lt;/p&gt;

&lt;p&gt;Now, let's talk about the Scikit-learn Pipeline module briefly.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Scikit-learn Pipeline&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;A Scikit-learn (Sklearn) pipeline is a powerful tool for streamlining, simplifying, and organizing machine learning workflows. It's essentially a way to automate a sequence of data processing and modeling steps into a single, cohesive unit. &lt;/p&gt;

&lt;p&gt;The Pipeline allows chaining together multiple data processing and modeling steps into a single, unified object.&lt;br&gt;
Modular Script + Pipeline Implementation = Best Industry Practices&lt;br&gt;
(The essence of this tutorial is to show how we can make use of the Sklearn Pipeline module in modular scripts for an even more streamlined industry standard)&lt;/p&gt;

&lt;p&gt;Here's a list of aspects of the machine learning process where Scikit-learn Pipeline can be used:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Data Preprocessing&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Imputing missing values.&lt;/li&gt;
&lt;li&gt;Scaling and standardizing features.&lt;/li&gt;
&lt;li&gt;Encoding categorical variables.&lt;/li&gt;
&lt;li&gt;Handling outliers.

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Feature Engineering&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creating new features or transforming existing ones.&lt;/li&gt;
&lt;li&gt;Applying dimensionality reduction techniques (e.g., PCA)

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. Model Training and Evaluation&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Constructing a sequence of data preprocessing and modeling steps.&lt;/li&gt;
&lt;li&gt;Cross-validation and evaluation.

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. Hyperparameter Tuning&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Using Grid Search or Random Search to find optimal hyperparameters.

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;5. Model Selection&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Comparing different models with the same preprocessing steps.&lt;/li&gt;
&lt;li&gt;Selecting the best model based on cross-validation results.

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;6. Prediction and Inference&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Applying the entire preprocessing and modeling pipeline to new data.

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;7. Deployment and Production&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Packaging the entire pipeline into a deployable unit.

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;8. Pipeline Serialization and Persistence&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Saving the entire pipeline to disk for future use.

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before demonstrating a few of these steps with a sample dataset, it is worth mentioning that the general outlook of Scikit-learn Pipeline loosely tends to follow this pattern:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Importing Necessary Libraries&lt;/li&gt;
&lt;li&gt;Creating Individual Transformers and Estimators&lt;/li&gt;
&lt;li&gt;Constructing the Pipeline&lt;/li&gt;
&lt;li&gt;Fitting and Transforming Data&lt;/li&gt;
&lt;li&gt;Fitting the Final Estimator

&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Advantages of Using Scikit-learn Pipeline&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Don’t forget, from our previous article, the purpose is to demonstrate modular codes in data science problems, then in this follow-up, we are adding more enhancements to our code, to make it robust by the use of Pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Scikit-learn Pipeline offers a range of advantages&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Simplicity and Readability of Code&lt;/strong&gt;: This can not be overemphasized. Sequences of data processing steps can be organized and structured into a single unit of codes, as we will soon see with an example. This will make the codes to be readable and then easily maintainable. This advantage of assembling multiple steps (transformers and an estimator) often results in a seamless workflow.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prevention of Data Leakage&lt;/strong&gt;: Scikit-learn Pipeline will take care of likely data leakage. To be clear about what data leakage is; data leakage occurs when your machine learning model gets access to information during training that it shouldn't have. It is an unintended exposure or mixing of data between training and test datasets. As a result, machine learning model performance will appear very great during training but otherwise when faced with new and unseen data. Using the Scikit-learn Pipeline is one of the ways to avoid data leakage.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ease of Deployment and productionization&lt;/strong&gt;: Pipelines also make the transition from model development to model deployment known as productionization of machine learning models easy. This is achieved by the Pipeline ensuring that all the steps and the model training are applied consistently in the same order and then encapsulate the entire workflow from preprocessing to deployment&lt;br&gt;
&lt;br&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Illustration of Scikit-learn Pipeline with Tips Dataset&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;For illustration, we will use the shipped seaborn dataset to demo a few of these steps (as this data is quite a simple one):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Data Preprocessing (Imputation, Scaling, Encoding)&lt;/li&gt;
&lt;li&gt;Model Training and Evaluation (including Cross-Validation)&lt;/li&gt;
&lt;li&gt;Hyperparameter Tuning (GridSearchCV)&lt;/li&gt;
&lt;li&gt;Prediction and Inference&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;
  
  
  About the dataset&lt;a&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;A few things about the data we will be making use of:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;The Tips dataset is part of the Seaborn data repository which serves as an illustrative example within the Seaborn package.&lt;/li&gt;
  &lt;li&gt;You can easily load the Tips dataset using the sns.load_dataset("tips") command.&lt;/li&gt;
  &lt;li&gt;The dataset contains information related to restaurant tips and comes with these variables/features:
    &lt;ul&gt;
      &lt;li&gt;total_bill: The total bill amount.&lt;/li&gt;
      &lt;li&gt;tip: The tip amount.&lt;/li&gt;
      &lt;li&gt;time: Whether the meal was during lunch or dinner.&lt;/li&gt;
      &lt;li&gt;smoker: Whether the customer is a smoker.&lt;/li&gt;
      &lt;li&gt;size: The size of the dining party.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We want to make a (binary) classification problem out of this dataset, whereby given the features, we would predict if the tip feature is greater than 5 or less. If the tip is greater than 5, our target (y) which we want to predict will be 1, but if the tip is less than 5, our y will be 0. Do you understand?&lt;br&gt;
Let’s get at it.&lt;/p&gt;
&lt;h4&gt;
  
  
  Step 1: Load the dataset&lt;a&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;We will load the dataset we described above with the code snippet here. We will save it as &lt;strong&gt;load_data.py&lt;/strong&gt;. We aim to use both modular code and Scikit-learn Pipeline to show how to approach machine learning problems.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import seaborn as sns #import the seaborn library

#load the tips dataset from seaborn library
def load_tips_data():
    df = sns.load_dataset('tips')
    return df

if __name__ == "__main__":
    tips_data = load_tips_data()
    print(tips_data.head())
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flmtdndlvdka30cpy0rlj.JPG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flmtdndlvdka30cpy0rlj.JPG" alt="load_output" width="443" height="129"&gt;&lt;/a&gt;&lt;br&gt;
&lt;br&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Step 2: Preprocessing Pipeline&lt;a&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;At this stage, we will apply the preprocess Pipeline to our data previously loaded in the load_data.py. We will save the script as &lt;strong&gt;preprocess_data.py&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.impute import SimpleImputer
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from load_data import load_tips_data #to load_data.py script

class PreprocessingTransformer(BaseEstimator, TransformerMixin):
    def __init__(self):
        self.numeric_features = ['total_bill', 'size']
        self.categorical_features = ['sex', 'smoker', 'day', 'time']

        self.numeric_preprocessor = Pipeline(
            steps=[('imputer', SimpleImputer(strategy='mean')),('scaler', StandardScaler())
        ])

        self.categorical_preprocessor = Pipeline(
            steps=[('imputer', SimpleImputer(fill_value='missing', strategy='constant')),
            ('onehot', OneHotEncoder(handle_unknown='ignore'))
        ])

        self.preprocessor = ColumnTransformer(
            transformers=[('numeric', self.numeric_preprocessor, self.numeric_features),
                ('categorical', self.categorical_preprocessor, self.categorical_features)
            ]
        )

    def fit(self, X, y=None):
        self.preprocessor.fit(X, y)
        return self

    def transform(self, X):
        return self.preprocessor.transform(X)

# Create an instance of PreprocessingTransformer
preprocessor_instance = PreprocessingTransformer()

# Load and preprocess data
tips_data = load_tips_data() # load_tips_data from the first script
X = tips_data.drop('tip', axis=1)
y = (tips_data['tip'] &amp;gt; 5).astype(int)

# Fit the preprocessor &amp;amp; transform the data
preprocessor_instance.fit(X, y)
transformed_data = preprocessor_instance.transform(X)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let us see so far what our codes have done to the Tips dataset:&lt;/p&gt;

&lt;p&gt;The preprocessing steps are as follows:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
&lt;strong&gt;Feature Selection&lt;/strong&gt;: The features are divided into numeric (total_bill, size) and categorical (sex, smoker, day, time) features.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Numeric Preprocessing&lt;/strong&gt;: The numeric features are preprocessed using a pipeline that includes:&lt;/li&gt;
  &lt;ul&gt;
    &lt;li&gt;Imputation: Missing values in the numeric features are replaced with the mean value of the respective feature.&lt;/li&gt;
    &lt;li&gt;Scaling: The numeric features are scaled to have zero mean and unit variance. This is done using the StandardScaler which standardizes features by removing the mean and scaling to unit variance.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;li&gt;
&lt;strong&gt;Categorical Preprocessing&lt;/strong&gt;: The categorical features are preprocessed using a pipeline that includes:&lt;/li&gt;
  &lt;ul&gt;
    &lt;li&gt;Imputation: Missing values in the categorical features are replaced with the constant string ‘missing’.&lt;/li&gt;
    &lt;li&gt;One-Hot Encoding: The categorical features are one-hot encoded. This means that each unique value in each categorical feature is turned into its binary feature.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;li&gt;
&lt;strong&gt;Column Transformation&lt;/strong&gt;: The ColumnTransformer applies the appropriate preprocessing pipeline to each subset of features (numeric and categorical).&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Fitting and Transforming&lt;/strong&gt;: The preprocessing transformer is fitted to the data, learning any parameters necessary for imputation and scaling. It then transforms the data according to the fitted parameters.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To see the output. If we use &lt;em&gt;print(transformed_data)&lt;/em&gt;, our output looks as below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Firfjxzt3tva741yqaayx.JPG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Firfjxzt3tva741yqaayx.JPG" alt="Processed Output" width="510" height="232"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The output of the code is a Numpy array (transformed_data). This represents the preprocessed features of the dataset. This data has been scaled and encoded, which is why it does not look easily interpretable like the actual Tips dataset we began with.&lt;/p&gt;

&lt;p&gt;Let’s move on to train the preprocessed data. (Assuming we are dealing with other datasets that require more preprocessed steps, our codes above would have to be more inclusive and accommodating to allow for all the necessary preprocessed steps.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Step 3: Training Pipeline&lt;a&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;In this third step, we will introduce the training Pipeline, where the two previous scripts will be inputs. We will save this as &lt;strong&gt;train_model.py&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score
from load_data import load_tips_data # accessing the load_data script
from preprocess_data import PreprocessingTransformer # accessing the preprocess_data script
from sklearn.pipeline import Pipeline

class TrainAndEvaluateModelTransformer(BaseEstimator, TransformerMixin):
    def __init__(self):
        pass

    def fit(self, X, y):
        X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

        self.model = RandomForestClassifier(random_state=42)
        self.model.fit(X_train, y_train)

        return self

    def transform(self, X):
        y_pred = self.model.predict(X)
        return y_pred

if __name__ == "__main__":
    tips_data = load_tips_data()  # Load and preprocess data
    preprocessor = PreprocessingTransformer()  # Create an instance without passing data
    preprocessed_data = preprocessor.fit_transform(tips_data)  # Fit and transform
    X = preprocessed_data
    y = (tips_data['tip'] &amp;gt; 5).astype(int)

    # Create a pipeline with TrainAndEvaluateModelTransformer
    model_pipeline = Pipeline([('model', TrainAndEvaluateModelTransformer())]) 

    # Fit and evaluate the model
    y_pred = model_pipeline.fit_transform(X, y)
    accuracy = accuracy_score(y, y_pred)
    print(f"Model Accuracy: {accuracy}")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is the summary of what happened in the train_model script.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create Model Pipeline&lt;/strong&gt;: A pipeline is created with TrainAndEvaluateModelTransformer. This transformer splits the data into training and testing sets, trains a RandomForestClassifier on the training data, and then uses this trained model to make predictions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fit and Evaluate the Model&lt;/strong&gt;: The model pipeline is fitted to the data and used to make predictions. The accuracy of these predictions is then calculated and printed to the console.
The output of this script will be the accuracy of the model, which is the proportion of correct predictions made by the model. 
This will be printed to the console as a decimal between 0 and 1, with 1 indicating perfect accuracy.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let’s take a look at the hyperparameter tuning Pipeline. &lt;br&gt;
&lt;br&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Step 4: Hyperparameter Pipeline&lt;a&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;Even though our model accuracy was not bad in the last step, the reason for this hyperparameter stage is to demonstrate also how we can use Pipeline for hyperparameter tuning. And we will also be saving the code as &lt;strong&gt;hyperparameter_tuning.py&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from sklearn.model_selection import GridSearchCV
from sklearn.ensemble import RandomForestClassifier
from load_data import load_tips_data
from preprocess_data import PreprocessingTransformer  # Import the PreprocessingTransformer
from sklearn.pipeline import Pipeline

if __name__ == "__main__":
    # Load and preprocess data
    tips_data = load_tips_data()
    X = tips_data.drop('tip', axis=1)
    y = (tips_data['tip'] &amp;gt; 5).astype(int)

    # Create a pipeline for preprocessing and model training
    model_pipeline = Pipeline([
        ('preprocessor', PreprocessingTransformer()),  # Use the custom preprocessing transformer
        ('model', RandomForestClassifier(random_state=42))
    ])

    # Define hyperparameter grid
    param_grid = {
        'model__n_estimators': [50, 100, 150],
        'model__max_depth': [None, 10, 20],
        'model__min_samples_split': [2, 5, 10]
    }

    # Perform GridSearchCV
    grid_search = GridSearchCV(model_pipeline, param_grid, cv=5)
    grid_search.fit(X, y)

    # Get the best tuned model
    best_tuned_model = grid_search.best_estimator_

    print("Best Hyperparameters:", best_tuned_model.named_steps['model'].get_params())
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Briefly, the hyperparameter Pipeline code defines the &lt;strong&gt;hyperparameter grid&lt;/strong&gt; (includes different values for several hyperparameters), &lt;strong&gt;performs the grid search&lt;/strong&gt; (tries different combinations of hyperparameters which results in the best score), &lt;strong&gt;gets the best model&lt;/strong&gt; with the best hyperparameters combination, and lastly, print out the very &lt;strong&gt;best hyperparameters&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is our output, the best hyperparameters, as printed by the last line of code:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foffllt7hpb9rit2hyiwn.JPG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foffllt7hpb9rit2hyiwn.JPG" alt="hyperparameter tuning" width="695" height="115"&gt;&lt;/a&gt;&lt;br&gt;
&lt;br&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Step 5: Serialize the model&lt;a&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;Finally, these whole modular scripts can be serialized and made ready for another program to be used as a pickled file (in production). We will save the script as &lt;strong&gt;serialized_model.py&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import joblib # for saving and loading Python objects to/from disk
from load_data import load_tips_data
from preprocess_data import PreprocessingTransformer
from train_model import TrainAndEvaluateModelTransformer
from hyperparameter_tuning import GridSearchCV, RandomForestClassifier

def serialize_model(model, filename):
    joblib.dump(model, filename)
    print(f"Model serialized and saved as '{filename}'")

if __name__ == "__main__":
    data = load_tips_data()
    X = PreprocessingTransformer().fit_transform(data)
    y = (data['tip'] &amp;gt; 5).astype(int)
    trained_model = TrainAndEvaluateModelTransformer().fit(X, y)
    tuned_model = GridSearchCV(RandomForestClassifier(random_state=42), param_grid={
        'n_estimators': [50, 100, 150],
        'max_depth': [None, 10, 20],
        'min_samples_split': [2, 5, 10]
    }, cv=5).fit(X, y)
    serialize_model(tuned_model, "best_model.pkl")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This last code will take a bit longer to finish executing because it involves all the steps (the Pipelines), and then the serializing (“pickling”) of the best model into a file.&lt;br&gt;
The output of our code will be a print statement indicating that the model has been serialized and saved as ‘&lt;strong&gt;best_model.pkl&lt;/strong&gt;’. We won’t see the model itself in the console, but a new file named ‘&lt;strong&gt;best_model.pkl&lt;/strong&gt;’ will be created in our current working directory. This same file contains our trained and tuned model. (To load the model back into memory, we can use &lt;strong&gt;joblib.load('best_model.pkl')&lt;/strong&gt;. This will return the model object)&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;In conclusion, we made attempts to show how to use modular scripts in conjunction with the Scikit-learn Pipeline module (with its classes) - way beyond what is demonstrated in many online courses whereby the Jupyter Notebook is portrayed as the only and best way to make data science projects work. Machine learning projects can be better streamlined, going by the industry's best approaches.&lt;br&gt;
The codes, the approaches, and the structures used here are never cast in stone; depending on the aim, nature, and goal of a data science/machine learning project, flexibility is always very much allowed.&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>datascience</category>
      <category>scikitlearn</category>
      <category>python</category>
    </item>
    <item>
      <title>User-Defined Functions in SQL: Expanding Your Database Toolkit</title>
      <dc:creator>Dare Johnson</dc:creator>
      <pubDate>Fri, 06 Oct 2023 15:42:01 +0000</pubDate>
      <link>https://dev.to/dare_johnson/user-defined-functions-in-sql-expanding-your-database-toolkit-lfh</link>
      <guid>https://dev.to/dare_johnson/user-defined-functions-in-sql-expanding-your-database-toolkit-lfh</guid>
      <description>&lt;p&gt;Why would you need to be able to create your own functions (UDF) in SQL? What is a typical function in SQL, and by extension, &lt;strong&gt;User-Defined Functions&lt;/strong&gt; (UDF)?&lt;/p&gt;

&lt;p&gt;Either as a seasoned SQL developer or random SQL user, it can't be overstated why you need to know how to create your own function.&lt;br&gt;
Aside the fact that SQL developers write complex queries, design databases (table, schema, etc.), they also go advanced a bit into creating Stored proc and even complex functions to make their work an ease.&lt;/p&gt;

&lt;p&gt;In order to avoid the need to define all the very basics, I will assume the reader is at least an immediate SQL user. But well, a beginner SQL analyst reading this would still gain one thing or the other. Before you read the last paragraph in this article, you would have known how to go about creating a &lt;strong&gt;User-Defined Functions&lt;/strong&gt; (UDF), and some more.&lt;br&gt;
&lt;br&gt;&lt;br&gt;
A SQL &lt;strong&gt;function&lt;/strong&gt; being a type of routine (program) is such that can be created to perform various tasks.&lt;/p&gt;

&lt;p&gt;Broadly speaking, there are two types of functions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Built-in Functions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;User-Defined Functions (UDFs)&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;While Built-in Functions come in different kinds (Aggregate, Ranking, String, Date/DateTime, etc.), UDFs also their types, as we will see shortly.&lt;/p&gt;

&lt;p&gt;By the way, let's go practical.&lt;/p&gt;

&lt;p&gt;I will be using &lt;a href="https://www.microsoft.com/en-us/sql-server/sql-server-downloads" rel="noopener noreferrer"&gt;Microsoft SQL Server&lt;/a&gt; to do the demo in this article, while also using some data readily available online - &lt;a href="https://github.com/Microsoft/sql-server-samples/releases/download/adventureworks/AdventureWorksDW2014.bak" rel="noopener noreferrer"&gt;AdventureWorksDW2014&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Question&lt;/strong&gt;:&lt;br&gt;
Create a User-Defined Function to calculate total sum of sales for any specified product between two dates.&lt;/p&gt;

&lt;p&gt;This is fairly simple one, but we will start somewhere.&lt;/p&gt;

&lt;p&gt;Writing this as a function, it would look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE FUNCTION dbo.CalcProductTotalSales
(@ProductKey INT, @start_date DATETIME, @end_date DATETIME)
RETURNS DECIMAL(18, 2)
AS
BEGIN
    DECLARE @UnitPrice MONEY;

    SELECT @UnitPrice = SUM(UnitPrice)
    FROM [dbo].[FactInternetSales]
    WHERE ProductKey = @ProductKey
    AND OrderDate BETWEEN @start_date AND @end_date;

    RETURN @UnitPrice;
END;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sincerely speaking, it may not look so attractive, right?&lt;br&gt;
Let me break it down: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Between BEGIN and END is the actual SELECT statement which does the analysis;&lt;/li&gt;
&lt;li&gt;"CREATE" keyword is common in SQL; it is used to create objects such as TABLES, DATABASES, VIEWS, etc., in this regard we used it to create a FUNCTION, thus the FUNCTION keyword; and the function name is &lt;strong&gt;dbo.CalcProductTotalSales&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Parentheses that follows function name specify the necessary variables to be made use of in the body of the function (between BEGIN and END keywords);&lt;/li&gt;
&lt;li&gt;As many "@" seen in the function shows the individual variables needed within the function. "@" symbol is used to introduce variables in SQL Server. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;While the function above is executed within SQL Server query editor (query window), it becomes stored away as seen below in the programmability folder within the database: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwgxpmlbglua777kzmbvj.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwgxpmlbglua777kzmbvj.jpg" alt="Function saved"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The implication of this is that even if &lt;strong&gt;SQL Server Management Studio&lt;/strong&gt; (SSMS) is shut down, that particular function created remains permanent, and can continually be accessed, next time you restart your server.&lt;/p&gt;

&lt;p&gt;But then, how do we then use it?&lt;br&gt;
It is by simply using the usual &lt;strong&gt;SELECT&lt;/strong&gt; statement, along with the function name, while supplying the parameters (productkey, start_date and end_date), as below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT dbo.CalcProductTotalSales(310, '2010-12-29 00:00:00.000', '2014-01-28 00:00:00.000');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you recall the question to which we created User-Defined Function (to calculate total sum of sales for any specified product between two dates), you would know that, even without using a function approach, we can as well easily write a SELECT query to carry out our analysis. In fact, the SELECT statement below does same thing as our function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT SUM(UnitPrice) as total_revenue
FROM [dbo].[FactInternetSales]
WHERE ProductKey = 310
AND OrderDate BETWEEN '2010-12-29 00:00:00.000' AND '2014-01-28 00:00:00.000';

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flhzrg8oyip6ceb02bb6l.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flhzrg8oyip6ceb02bb6l.jpg" alt="Same result"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But then, why would we desire to create a function to an obvious SQL problem which needs a mere SELECT statement to solve?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;An obvious reason is, if there is no SQL built-in function readily available to solve a desired analysis in SQL, then User-Defined Function (UDF) can answer to the call. (The UDF example given above is such a simple one, used for illustration purpose)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Secondly, if there be need to keep re-using a particular query in further future analysis, we can as well make a function of it. (SQL VIEWs and Stored Procedures share similar application as this, among many others).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lastly, UDFs help to simplify development by encapsulating (complex) business logic and make them easily available for reuse.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The advantage of using function includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Code reusability &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Simplicity&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Consistency&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Modularity&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Having introduced quite a straightforward function to prove a point, let's see more reason why you will come to a point where you will need to create your own function someday - if you have never hitherto!&lt;/p&gt;

&lt;p&gt;Do you recall CAST() function? &lt;/p&gt;

&lt;p&gt;This very function which converts one data type to the other? The reason why you don't get to create it afresh any time you need it is because it comes handy, already built-in in SQL and all you need to do is just to call it as at when needed.&lt;br&gt;
If you were to create CAST function which can convert CHAR (string) to INT (integer) alone, error handling not factored in, it would look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE FUNCTION dbo.CastAsInt(@value NVARCHAR(MAX))
RETURNS INT
AS
BEGIN
    DECLARE @result INT;

    SELECT @result = CAST(@value AS INT);

    RETURN @result;
END;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, the fact that someday, you will need a function within SQL which is not natively available (or built-in), it is for this reason that it is essential to master how to craft your own UDFs, User-Defined Functions. &lt;br&gt;
SQL creators knew that it would be impossible to factor in every functions one could ever think of as a built-in function, then the template to be able to construct one yourself is provided for.&lt;/p&gt;

&lt;p&gt;Let us assume that we want to be able to calculate a person's age given the person's date of birth: that's, a formula which can calculate age from date of birth; &lt;br&gt;
There is not any built-in function in SQL Server for such. What is close to this function is using the combination of &lt;strong&gt;DATEDIFF&lt;/strong&gt; alongside &lt;strong&gt;GETDATE()&lt;/strong&gt; to calculate the difference in YEAR.&lt;/p&gt;

&lt;p&gt;(There is the &lt;strong&gt;AGE()&lt;/strong&gt; function to calculate same in &lt;a href="https://www.postgresql.org/" rel="noopener noreferrer"&gt;&lt;strong&gt;PostgreSQL&lt;/strong&gt;&lt;/a&gt;. &lt;a href="https://www.mysql.com/" rel="noopener noreferrer"&gt;&lt;strong&gt;MySQL&lt;/strong&gt;&lt;/a&gt; also has &lt;strong&gt;TIMESTAMPDIFF()&lt;/strong&gt; function which is not that straightforward too)  &lt;/p&gt;

&lt;p&gt;To create a permanent function which can be used again and again in SQL Server, let's see how this can be done, we will call it &lt;strong&gt;dbo.CalculateAge&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE FUNCTION dbo.CalculateAge(@dob DATE)
RETURNS INT
AS
BEGIN
    DECLARE @age INT;

    SELECT @age = DATEDIFF(YEAR, @dob, GETDATE()) - 
                  CASE 
                      WHEN MONTH(@dob) &amp;gt; MONTH(GETDATE()) OR (MONTH(@dob) = MONTH(GETDATE()) AND DAY(@dob) &amp;gt; DAY(GETDATE()))
                      THEN 1 
                      ELSE 0 
                  END;

    RETURN @age;
END;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No more, no less.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NB&lt;/strong&gt;: The "&lt;strong&gt;dbo&lt;/strong&gt;" is the very schema name which differentiates it from any other schema. It could be another name, but "&lt;strong&gt;dbo&lt;/strong&gt;" (database owner) here is the default schema while the database/table was created. &lt;/p&gt;

&lt;p&gt;If you supply any column or data (of date/datetime data type) to the function &lt;strong&gt;dbo.CalculateAge&lt;/strong&gt;, it gives you the age on the go, without having to continually write the whole DATEDIFF, GETDATE functions again.&lt;br&gt;
For instance,&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi006w1943yvjsxnhj4jx.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi006w1943yvjsxnhj4jx.jpg" alt="Customer's Age"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What function can you think of again in SQL (SQL Server) which is not built-in? They can be many, depending on the use case.&lt;/p&gt;

&lt;p&gt;&lt;br&gt;&lt;br&gt;
Having said all these and explained why SQL developer should be able to create a UDF personally, let's briefly look at how to create one, making use of the template already provided in SSMS.&lt;br&gt;
But foremost, this is worth mentioning, we have about 3 types of SQL User Defined Functions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Scalar Functions (returns a single data value of the type defined in the RETURNS clause)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Table-Valued Functions (returns a table data type)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Inline Table_Valued Functions (returns a table data type)&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;br&gt;&lt;br&gt;
To create any type of function using the templates inside SSMS, &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Right click the query editor and click &lt;strong&gt;Insert Snippet&lt;/strong&gt;: &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy8uvyfpzuuk1z9to7pcw.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy8uvyfpzuuk1z9to7pcw.jpg" alt="Function"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click on &lt;strong&gt;Function&lt;/strong&gt;:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F47ymo3elpblx19kz8jxh.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F47ymo3elpblx19kz8jxh.jpg" alt="Function2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Choose the type of function you desire to create based on what it returns (scalar, table)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjhh4wsiriqzu5lzly20t.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjhh4wsiriqzu5lzly20t.jpg" alt="Function3"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Choosing the first option (&lt;strong&gt;Create Inline Table Function&lt;/strong&gt;):&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb3n8g8tj2dpqrx0i80rs.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb3n8g8tj2dpqrx0i80rs.jpg" alt="Function4"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There, you have the template. From the template, you can build your desired function.&lt;br&gt;
(All the examples shown earlier are of Scalar type - because the function returns a single value) &lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Final Note &amp;amp; Considerations&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There are a few considerations to keep in mind when creating User-Defined Functions (UDFs) in SQL Server:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;UDFs can’t be used to perform actions that modify the database state.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Error handling is restricted in a UDF. A UDF doesn’t support TRY...CATCH, @ ERROR or RAISERROR1.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;UDFs can’t call a stored procedure but can call an extended stored procedure. In likewise manner, UDFs can’t return multiple result sets. Use a stored procedure if you need to return multiple result sets.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;UDFs can be nested; that is, one user-defined function can call another. The nesting level is incremented when the called function starts execution, and decremented when the called function finishes execution. In addition, user-defined functions can be nested up to 32 levels.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There you go! As you explore the more of the power of SQL data sleuthing, don't forget to consider what typical UDFs can do to aid your analyses.&lt;/p&gt;

</description>
      <category>sqlserver</category>
      <category>rdbms</category>
      <category>datascience</category>
      <category>sql</category>
    </item>
  </channel>
</rss>
