Q- i have create this so far 

import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt


path="data/water_potability.csv"
df=pd.read_csv(path)
df.head()


df.shape


df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 3276 entries, 0 to 3275
Data columns (total 10 columns):
 #   Column           Non-Null Count  Dtype  
---  ------           --------------  -----  
 0   ph               2785 non-null   float64
 1   Hardness         3276 non-null   float64
 2   Solids           3276 non-null   float64
 3   Chloramines      3276 non-null   float64
 4   Sulfate          2495 non-null   float64
 5   Conductivity     3276 non-null   float64
 6   Organic_carbon   3276 non-null   float64
 7   Trihalomethanes  3114 non-null   float64
 8   Turbidity        3276 non-null   float64
 9   Potability       3276 non-null   int64  
dtypes: float64(9), int64(1)
memory usage: 256.1 KB

df.describe()
""""
now we need to get the percentage of null valus


"""




Q- oky the task in our hand here is to create a “genrale “ function that leverage the functional programming paradigms , using pure function principle oky 
“Pure functions are a foundational concept in functional programming, characterized by specific principles that enhance code reliability and maintainability. Here are the three key principles of pure functions:

## Determinism
A pure function is deterministic, meaning it will always produce the same output when given the same input. This predictability simplifies debugging and reasoning about code, as developers can trust that the function's behavior remains consistent across different contexts[1][3].

## No Side Effects
Pure functions do not cause side effects, which means they do not modify any external state or data outside their own scope. They solely rely on their input parameters to compute their output, making them easier to understand and test[2][4].

## Referential Transparency
Referential transparency implies that a function can be replaced with its output value without altering the program's behavior. This property allows for easier reasoning about code and facilitates safe transformations and optimizations[1][4].

These principles collectively contribute to the robustness and clarity of functional programming, making it a preferred paradigm for many developers.

Citations:
[1] https://adabeat.com/fp/pure-functions-in-functional-programming/
[2] https://blog.rheinwerk-computing.com/what-are-the-principles-of-functional-programming
[3] https://maddevs.io/blog/functional-programming-principles-in-javascript/
[4] https://dev.to/jamesrweb/principles-of-functional-programming-4b7c
[5] https://stackoverflow.com/questions/65185253/functional-programming-principles-vs-functional-programing-paradigm
[6] https://www.freecodecamp.org/news/the-principles-of-functional-programming/
[7] https://redux.js.org/understanding/thinking-in-redux/three-principles
[8] https://dev.to/rosselli00/functional-programming-fp-principles-with-examples-pd3
“ 

So the function only use the parameters of it’s arguments and only use copy of data to respect the data immutubilty principale oky ? 

Q- first let’s define the function signature definition ? 

Function : percentage_of_null_values (col:”DataFrameCol” : Serie ) : 

Q- my fitrst question is the signature defintion correct yes or non ? and tell me why 

No, the signature definition is not entirely correct. Here's why and how to fix it:
Issues with the original signature:
Type Hinting: col:"DataFrameCol" : Serie isn't standard Python type hinting. While the intent is clear, the syntax is incorrect. Python uses a different approach for type hints.
DataFrame Column vs. Series: A DataFrame column is a Series. Specifying both is redundant. It's better to just use Series. Furthermore, the function should be general enough to handle any iterable (like a list or a tuple) not just a Pandas Series.
Return Type: The function signature doesn't specify a return type. It should indicate that it returns a float (the percentage).
Corrected Signature:
     from typing import Iterable

def percentage_of_null_values(col: Iterable) -> float:
    """Calculates the percentage of null values in an iterable.

    Args:
        col: An iterable (e.g., list, tuple, Pandas Series).

    Returns:
        The percentage of null values as a float.
    """
    # ... function implementation ...
   
content_copy Use code with caution.Python
Explanation of Changes:
from typing import Iterable: Imports the Iterable type hint for better clarity and compatibility.
col: Iterable: Specifies that the col argument can be any iterable, making the function more versatile.
-> float: Indicates that the function returns a float value.
Example Implementation (Pure Function):
     from typing import Iterable

def percentage_of_null_values(col: Iterable) -> float:
    """Calculates the percentage of null values in an iterable.

    Args:
        col: An iterable (e.g., list, tuple, Pandas Series).

    Returns:
        The percentage of null values as a float.
    """
    count_null = sum(1 for x in col if pd.isnull(x)) # or (x is None) for general use
    total_count = len(col)
    if total_count == 0:
        return 0.0  # Handle empty iterables gracefully
    return (count_null / total_count) * 100
   
content_copy Use code with caution.Python
This implementation adheres to pure function principles:
Deterministic: Given the same input, it always produces the same output.
No Side Effects: It doesn't modify the input col or any external state. It uses a generator expression sum(1 for x in col ...) which avoids creating a temporary list, making it memory-efficient, especially for large datasets.
Referentially Transparent: The function call can be replaced by its return value without changing the program's behavior.
This improved version is more general, type-safe, and adheres to best practices for functional programming in Python. It will work with your Pandas DataFrame columns (which are Series) as well as other iterables.

Q- this my logic 

“""""
now we need to get the percentage of null valus


"""
from typing import Iterable


def percentage_of_null_values(col:Iterable)->float:
    """
        Calculates the percentages of null values in an iterable


        Args:
            col: An iterable (e.g  list , tuple , Pandas Series )
        Returns:
            The percentage of null valuses as float


    """
    count_null=sum(1 for x in col if pd.isnull(x))
    total_count=len(col)
    if total_count ==0:
        return 0.0
    return (count_null / total_count)*100


"""


now using lambada expression implment the function that iteate through each col in the dataFrame
and apply the "percentage_of_null_values" function to it


"""
null_percentages = df.apply(lambda col: percentage_of_null_values(col))
null_percentages
“ 

And this the return 

“ph                 14.
987790
Hardness            0.000000
Solids              0.000000
Chloramines         0.000000
Sulfate            23.840049
Conductivity        0.000000
Organic_carbon      0.000000
Trihalomethanes     4.945055
Turbidity           0.000000
Potability          0.000000
dtype: float64
“ 



Q- now because i am a software engineering with focus on ml i want to converte this part of the notebook  into an actual module code that follow the principle of software engineering 
Like clean code , oop , design pattern , 

Q- first thing using oop what this the best name of the class to encampselte this logic and tell me why ?