Advance Python Final Notes
Advance Python Final Notes
Data Types
Data types define the type of data a variable can hold. Common Python data types include:
Data Structures
Data structures are ways to organize and store multiple values efficiently. Common Python
structures include:
1
2
Sequence Types
Lists []
Ordered, mutable, heterogeneous collections.
1 fruits = [ ’ apple ’ , ’ banana ’ , ’ orange ’]
2 fruits . append ( ’ grape ’) # Add to end
3 fruits . insert (1 , ’ kiwi ’) # Insert at index
4 fruits . remove ( ’ banana ’) # Remove by value
5 popped = fruits . pop () # Remove and return last
6 sliced = fruits [1:3] # Slicing [ start : end : step ]
Tuples ()
Ordered, immutable, heterogeneous collections.
1 coordinates = (10 , 20)
2 x , y = coordinates # Tuple unpacking
3 single_item = (1 ,) # Single item needs trailing comma
Strings ’’ or ""
Ordered, immutable sequences of characters.
1 text = " Python Programming "
2 text . upper () # Returns new string
3 text . split () # Split into list
4 " " . join ([ ’ Python ’ , ’ Data ’ ]) # Join list into string
Ranges
Immutable sequences of numbers.
1 range (5) # 0, 1, 2, 3, 4
2 range (2 , 10 , 2) # 2, 4, 6, 8
Mapping Types
Dictionaries {}
Key-value pairs (Python 3.7+ preserves insertion order).
2
3
Set Types
set
Unordered collection of unique, hashable objects.
1 set1 = {1 , 2 , 3 , 4}
2 set2 = {3 , 4 , 5 , 6}
3 set1 . add (5) # Add element
4 set1 . remove (3) # Remove ( raises KeyError if missing )
5 set1 . discard (10) # Remove ( no error if missing )
6
7 # Set operations
8 set1 . union ( set2 ) # {1 , 2 , 3 , 4 , 5 , 6}
9 set1 . intersection ( set2 ) # {3 , 4}
10 set1 . difference ( set2 ) # {1 , 2}
11 set1 . s y m m etric_difference ( set2 ) # {1 , 2 , 5 , 6}
12 set1 . issubset ( set2 ) # Check if subset
frozenset
Immutable set (hashable, can be dict key).
1 immutable_set = frozenset ([1 , 2 , 3])
NoneType
Represents absence of value.
3
4
Control Flow
Conditional Statements
Conditional statements allow decision-making in code based on logical conditions.
1 # Basic if - elif - else
2 if condition1 :
3 # code block
4 elif condition2 :
5 # code block
6 else :
7 # code block
8
9 # Ternary expression
10 value = x if condition else y
11
12 # Pattern matching ( Python 3.10+)
13 match point :
14 case (0 , 0) :
15 print ( " Origin " )
16 case (x , 0) :
17 print ( f "X - axis at { x } " )
18 case (0 , y ) :
19 print ( f "Y - axis at { y } " )
20 case (x , y ) :
21 print ( f " Point at { x } , { y } " )
Loops
Loops allow repeated execution of code blocks.
For loops: Iterate over sequences.
1 for item in iterable :
2 # code block
3
4 # Enumerate for index and value
5 for i , value in enumerate ( iterable ) :
6 print ( f " Index { i }: { value } " )
7
8 # Zip for parallel iteration
9 for a , b in zip ( list1 , list2 ) :
10 print (a , b )
11
12 # Iterate over dictionary
13 for key , value in dictionary . items () :
14 print ( key , value )
4
5
Comprehensions
Concise way to create collections from iterables.
1 # List comprehension
2 squares = [ x **2 for x in range (10) ]
3 evens = [ x for x in range (20) if x % 2 == 0]
4 matrix = [[ i * j for j in range (3) ] for i in range (3) ]
5
6 # Dictionary comprehension
7 square_dict = { x : x **2 for x in range (5) }
8 filtered_dict = { k : v for k , v in original . items () if v > 10}
9
10 # Set comprehension
11 unique_lengths = { len ( word ) for word in words }
12
13 # Generator expressions
14 sum_of_squares = sum ( x **2 for x in range (1000000) )
15 gen = ( x **2 for x in range (5) )
16 print ( list ( gen ) ) # [0 , 1 , 4 , 9 , 16]
17 print ( list ( gen ) ) # [] ( exhausted )
Functions
First-Class Functions
Functions can be assigned, passed, and returned.
1 def greet ( name ) :
2 return f " Hello , { name } "
3
4 say_hello = greet
5 print ( say_hello ( " Alice " ) )
6
7 def apply_twice ( func , arg ) :
8 return func ( func ( arg ) )
9
10 def add_one ( x ) :
11 return x + 1
12
13 result = apply_twice ( add_one , 5) # 7
Lambda Functions
Anonymous, single-expression functions.
1 square = lambda x : x **2
2 add = lambda x , y : x + y
3
4 pairs = [(1 , ’ one ’) , (3 , ’ three ’) , (2 , ’ two ’) ]
5 pairs . sort ( key = lambda pair : pair [0])
6 evens = list ( filter ( lambda x : x % 2 == 0 , numbers ) )
7 squared = list ( map ( lambda x : x **2 , numbers ) )
5
6
Decorators
Functions that modify behavior of other functions.
1 def timer ( func ) :
2 import time
3 def wrapper (* args , ** kwargs ) :
4 start = time . time ()
5 result = func (* args , ** kwargs )
6 end = time . time ()
7 print ( f " { func . __name__ } took { end - start :.4 f } seconds " )
8 return result
9 return wrapper
10
11 @timer
12 def slow_function () :
13 import time
14 time . sleep (1)
15 return " Done "
16
17 # Decorators with arguments
18 def repeat ( n_times ) :
19 def decorator ( func ) :
20 def wrapper (* args , ** kwargs ) :
21 for _ in range ( n_times ) :
22 result = func (* args , ** kwargs )
23 return result
24 return wrapper
25 return decorator
26
27 @repeat (3)
28 def greet ( name ) :
29 print ( f " Hello { name } " )
Closures
Functions capturing variables from outer scope.
6
7
Error Handling
try, except, else, finally
Handle exceptions gracefully.
1 try :
2 risky_operation ()
3 except SomeSpecificError as e :
4 print ( f " Error occurred : { e } " )
5 except Exception as e :
6 print ( f " Unexpected error : { e } " )
7 else :
8 print ( " Operation successful ! " )
9 finally :
10 clean up_resources ()
Custom Exceptions
Define specific errors for clarity.
1 class DataScienceError ( Exception ) : pass
2 class D a t aValidationError ( DataScienceError ) : pass
3
4 class Mo delTrainingError ( DataScienceError ) :
5 def __init__ ( self , message , model_name = None ) :
6 self . model_name = model_name
7 super () . __init__ ( f " { message } ( Model : { model_name }) " )
8
9 # Usage
10 def train_model ( data , model_name ) :
11 if not data :
12 raise DataValidationError ( " Empty dataset provided " )
13 try :
14 # training code
15 pass
16 except Exception as e :
17 raise ModelTrainingError ( f " Training failed : { e } " , model_name )
18
19 # Handling custom exceptions
20 try :
7
8
Context Managers
Ensure resource management using with.
1 with open ( ’ file . txt ’ , ’r ’) as file :
2 content = file . read ()
3
4 # Custom context manager using contextlib
5 from contextlib import contextmanager
6
7 @contextmanager
8 def managed_file ( filename , mode = ’r ’) :
9 file = open ( filename , mode )
10 try :
11 yield file
12 finally :
13 file . close ()
14
15 # Multiple context managers
16 with open ( ’ input . txt ’) as infile , open ( ’ output . txt ’ , ’w ’) as outfile :
17 outfile . write ( infile . read () )
18
19 # Timing context manager
20 import time
21
22 @contextmanager
23 def timer () :
24 start = time . time ()
25 try :
26 yield
27 finally :
28 end = time . time ()
29 print ( f " Operation took { end - start :.4 f } seconds " )
Naming conventions
Naming conventions are a set of standardized rules and guidelines for choosing names for program
identifiers, such as variables, functions, classes, and constants. They ensure:
By following naming conventions, identifiers convey meaning about their purpose and scope.
8
9
Naming Conventions
• Variables and Functions: snake case
Code Layout
• 4 spaces per indentation level
• No tabs
Imports
• One import per line
Whitespace
• Space around binary operators: a + b, not a+b
9
10
Comments
• Inline comments start with #: # comment
String Quotes
• Consistency within a module
Line Breaks
• Break before binary operators
PEP 8 Examples
10
11
Imports Programs
1 # Standard Library
2 import os
3 import sys
4 import json
5 from datetime import datetime
6 from collections import defaultdict , Counter
7
8 # Third - party
9 import numpy as np
10 import pandas as pd
11 import matplotlib . pyplot as plt
11
12
14 # Local imports
15 from myproject . config import settings
16 from myproject . database import get_connection
17 from myproject . models import Customer
1 # Whitespace examples
2 x = 5 + 3
3 function_call ( arg1 , arg2 , arg3 )
4 user = { ’ name ’: ’ Alice ’ , ’ age ’: 30}
5
6 # Comments
7 def calculate_tax ( price , tax_rate ) :
8 " " " Calculate tax amount . " " "
9 # Adjust for tax - exempt items
10 if tax_rate < 0:
11 tax_rate = 0
12 return price * tax_rate
13
14 # Strings
15 name = " Alice "
16 message = f " { name } , welcome ! "
17 def multiply (a , b ) :
18 " " " Multiply two numbers . " " "
19 return a * b
20
21 # Line breaks
22 total = ( first_variable
23 + second_variable
24 - third_variable
25 * fourth_variable )
1 """
2 data_pipeline . py
3 Complete data processing pipeline demonstrating PEP 8 conventions .
4 """
5 import csv
6 import json
7 import logging
8 from pathlib import Path
9 from typing import List
10 import numpy as np
11 import pandas as pd
12 from sklearn . preprocessing import StandardScaler
13 from . config import settings
14 from . validators import validate_data
15
16 MAX_FILE_SIZE_MB = 100
17 DEFAULT_ENCODING = ’utf -8 ’
12
13
18
19 class DataPipeline :
20 def __init__ ( self , source_path : Path , destination_path : Path ) :
21 self . source_path = source_path
22 self . destination_path = destination_path
23 self . data = None
24 self . _processed = False
25
• Iterators – Objects that implement the iterator protocol with iter () and next ()
methods.
• Generators – Functions using yield or generator expressions that produce values lazily.
• Decorators – Functions or classes that modify behavior of other functions or classes using
@decorator syntax.
13
14
Collections Module
namedtuple
1 from collections import namedtuple
2
3 # Creating a namedtuple type
4 Point = namedtuple ( ’ Point ’ , [ ’x ’ , ’y ’ ])
5 p = Point (10 , 20)
6 print ( p .x , p . y ) # Access by name
7 print ( p [0] , p [1]) # Access by index also works
defaultdict
1 from collections import defaultdict
2
3 # int provides default value 0
4 word_counts = defaultdict ( int )
5 # list provides default empty list
6 grouped_data = defaultdict ( list )
7 # set provides default empty set
8 unique_sets = defaultdict ( set )
Counter
1 from collections import Counter
2
3 # Count elements in an iterable
4 freq = Counter ([ ’a ’ , ’b ’ , ’a ’ , ’c ’ , ’a ’ , ’b ’ ])
5 # Most common elements
6 top_two = freq . most_common (2)
OrderedDict
1 from collections import OrderedDict
2
3 od = OrderedDict ()
4 od [ ’ first ’] = 1
5 od [ ’ second ’] = 2
6 od . move_to_end ( ’ first ’) # Moves to end
deque
1 from collections import deque
2
14
15
ChainMap
Iterators
Iterator Protocol
1 class CountDown :
2 " " " Iterator that counts down from n to 1. " " "
3 def __init__ ( self , start ) :
4 self . current = start
5
6 def __iter__ ( self ) :
7 return self
8
9 def __next__ ( self ) :
10 if self . current <= 0:
11 raise StopIteration
12 self . current -= 1
13 return self . current + 1
Iterable vs Iterator
Infinite Iterators
15
16
Generators
Generator Functions
1 def countdown ( n ) :
2 " " " Generator that counts down from n . " " "
3 while n > 0:
4 yield n
5 n -= 1
6
7 gen = countdown (5)
8 print ( next ( gen ) ) # 5
9 print ( next ( gen ) ) # 4
Generator Expressions
Memory Efficiency
Generator Pipeline
16
17
4
5 def filter_data ( stream , condition ) :
6 for item in stream :
7 if condition ( item ) :
8 yield item
9
10 def transform_data ( stream , func ) :
11 for item in stream :
12 yield func ( item )
13
14 pipeline = transform_data (
15 filter_data (
16 read_data ( data_source ) ,
17 lambda x : x > 0
18 ),
19 lambda x : x ** 2
20 )
yield from
Theory: The yield from expression allows a generator to delegate part of its operations to
another generator or iterable. It yields all values from the sub-iterator, making it easier to
compose generators and flatten nested generator structures.
1 def chain (* iterables ) :
2 " " " Chain multiple iterables together . " " "
3 for iterable in iterables :
4 yield from iterable
5
6 # Equivalent to :
7 def chain_alt (* iterables ) :
8 for iterable in iterables :
9 for item in iterable :
10 yield item
Function Decorators
Theory: Decorators are functions that modify the behavior of other functions without changing
their source code. They use the @decorator syntax and are applied at function definition time.
17
18
Decorator Syntax
Theory: The @decorator syntax is syntactic sugar for function = decorator(function).
1 # These are equivalent
2 @decorator
3 def func () :
4 pass
5
6 # Same as
7 def func () :
8 pass
9 func = decorator ( func )
18
19
9
10 @logged
11 def add (x , y ) :
12 " " " Add two numbers . " " "
13 return x + y
14
15 print ( add . __name__ ) # ’ add ’
16 print ( add . __doc__ ) # ’ Add two numbers . ’
Class Decorators
Theory: Decorators can also be applied to classes to modify or enhance behavior.
1 def singleton ( cls ) :
2 " " " Decorator that ensures only one instance of class . " " "
3 instances = {}
4 def get_instance (* args , ** kwargs ) :
5 if cls not in instances :
6 instances [ cls ] = cls (* args , ** kwargs )
7 return instances [ cls ]
8 return get_instance
9
10 @singleton
11 class Da tabaseConnection :
12 def __init__ ( self ) :
13 self . connected = False
Property Decorator
Theory: The @property decorator transforms methods into managed attributes.
1 class Temperature :
2 def __init__ ( self , celsius ) :
3 self . _celsius = celsius
4
5 @property
6 def celsius ( self ) :
7 return self . _celsius
8
9 @property
10 def fahrenheit ( self ) :
11 return ( self . _celsius * 9/5) + 32
12
13 @celsius . setter
14 def celsius ( self , value ) :
15 if value < -273.15:
16 raise ValueError ( " Temperature below absolute zero " )
17 self . _celsius = value
19
20
Stacked Decorators
Theory: Multiple decorators can be stacked; execution order is bottom to top.
1 @timer
2 @logged
3 @memoize
4 def fibonacci ( n ) :
5 " " " Calculate nth Fibonacci number . " " "
6 if n < 2:
7 return n
8 return fibonacci (n -1) + fibonacci (n -2)
Decorator Factories
Theory: Factories parameterize decorators.
1 def rate_limit ( max_calls , period ) :
2 " " " Decorator factory for rate limiting . " " "
3 def decorator ( func ) :
4 calls = []
5 @wraps ( func )
6 def wrapper (* args , ** kwargs ) :
7 from time import time
8 now = time ()
9 calls [:] = [ c for c in calls if now - c < period ]
10 if len ( calls ) >= max_calls :
11 raise Exception ( " Rate limit exceeded " )
12 calls . append ( now )
13 return func (* args , ** kwargs )
14 return wrapper
15 return decorator
16
17 @rate_limit ( max_calls =5 , period =60)
18 def api_call () :
19 " " " Limited to 5 calls per minute . " " "
20 return " API response "
20
21
Integration Examples
21
22
Functions
Key Characteristics:
Return Values
Python functions can return multiple values by packing them into a tuple. Functions without a
return statement return None.
1 def real_imag_conj ( val ) :
2 " " " Return real part , imaginary part , and conjugate . " " "
3 return val . real , val . imag , val . conjugate ()
4
5 r , i , c = real_imag_conj (3 + 4 j )
6 print (r , i , c ) # 3.0 4.0 (3 -4 j )
7
8 def print_message ( msg ) :
9 print ( msg )
10
11 result = print_message ( " Hello " )
12 print ( result ) # None
22
23
6 return L
7
8 print ( fibonacci (10) ) # Defaults
9 print ( fibonacci (10 , 0 , 2) ) # Positional override
10 print ( fibonacci (10 , b =3 , a =1) ) # Keyword override
11
12 def add_item ( item , L =[]) : # Dangerous !
13 L . append ( item )
14 return L
15
16 def add_item_safe ( item , L = None ) :
17 if L is None :
18 L = []
19 L . append ( item )
20 return L
Variable-Length Arguments
23
24
Lambda Expressions
24
25
11
12 # reduce
13 from functools import reduce
14 total = reduce ( lambda x , y : x +y , [1 ,2 ,3 ,4 ,5])
Higher-Order Functions
Function Composition
Combine functions to form new functions.
1 def compose (f , g ) :
2 return lambda x : f ( g ( x ) )
3
4 add_then_square = compose ( lambda x : x **2 , lambda x : x +1)
5 print ( add_then_square (3) )
Function Decorators
25
26
Scoping Rules
LEGB Rule
Python resolves names using Local, Enclosing, Global, Built-in scopes.
1 x = " global "
2
3 def outer () :
4 x = " enclosing "
5 def inner () :
6 x = " local "
7 print ( " inner : " , x )
8 inner ()
9 print ( " outer : " , x )
10
11 outer ()
12 print ( " global : " , x )
13
14 # nonlocal example
15 def counter () :
16 count = 0
17 def increment () :
18 nonlocal count
19 count += 1
20 return count
21 return increment
22
23 c = counter ()
24 print ( c () )
25 print ( c () )
Summary
Concept Purpose Syntax Best Used For
def functions Named, reusable code def name(params): Complex logic, reuse
Lambda Anonymous, single-expression lambda params: expr Simple callbacks, key functions
*args Variable positional args def f(*args): Wrappers, variable inputs
**kwargs Variable keyword args def f(**kwargs): Configuration, wrappers
Default args Optional parameters def f(x=default): Common-case values
Higher-order Functions as objects def f(): return g Composition, decorators
26
27
1 class DataProcessor :
2 " " " Template for processing data with configurable operations . " " "
3
4 def __init__ ( self , name : str , scale_factor : float = 1.0) :
5 """
6 Initialize processor with configuration .
7
8 Args :
9 name : Identifier for the processor instance
10 scale_factor : Multiplier to apply during processing
11 """
12 self . name = name # Public attribute
13 self . _scale_factor = scale_factor # Protected by convention
14 self . __processed_data = None # Private ( name mangling )
15 self . status = " initialized "
16 self . raw_data = None
17
18 def load_data ( self , data ) :
19 " " " Load data into the processor . " " "
20 print ( f " { self . name }: Loading data ... " )
21 self . raw_data = data
22 self . status = " loaded "
23
24 def process ( self ) :
25 " " " Apply configured transformations . " " "
26 if self . raw_data is not None :
27 print ( f " Processing with scale factor : { self . _scale_factor } " )
28 self . __processed_data = [ x * self . _scale_factor for x in self .
raw_data ]
29 self . status = " processed "
30 return self . __processed_data
31 else :
32 raise ValueError ( " No data loaded . Call load_data () first . " )
33
34 def get_results ( self ) :
35 " " " Safely access processed data . " " "
36 return self . __processed_data if self . __processed_data is not None else
None
37
38 def reset ( self ) :
39 " " " Reset processor state . " " "
40 self . __processed_data = None
41 self . raw_data = None
42 self . status = " reset "
43
44 def __repr__ ( self ) :
45 " " " Provide readable representation . " " "
46 return f " DataProcessor ( name = ’{ self . name } ’ , status = ’{ self . status } ’) "
47
48 def __len__ ( self ) :
49 " " " Return length of raw data if loaded . " " "
50 return len ( self . raw_data ) if self . raw_data else 0
51
52 # Usage Example
53 processor = DataProcessor ( " test_processor " , scale_factor =2.5)
54 print ( processor )
55
56 data = [1 , 2 , 3 , 4 , 5]
57 processor . load_data ( data )
58 result = processor . process ()
59 print ( f " Processed result : { result } " )
60 print ( f " Data length : { len ( processor ) } " )
27
28
28
29
58 return result
59
60 def fit_transform ( self , data ) :
61 " " " Convenience method combining fit and transform . " " "
62 self . fit ( data )
63 return self . transform ( data )
64
65 @property
66 def parameters ( self ) :
67 " " " Read - only access to parameters . " " "
68 return self . _parameters . copy ()
69
70 @property
71 def is_fitted ( self ) :
72 " " " Check if scaler has been fitted . " " "
73 return self . _fitted
74
75 # Usage Example
76 scaler = FeatureScaler ( method = ’ standard ’)
77 data = [10 , 20 , 30 , 40 , 50]
78
79 # Fit and transform
80 scaled_data = scaler . fit_transform ( data )
81 print ( f " Scaled data : { scaled_data } " )
82 print ( f " Parameters : { scaler . parameters } " )
83
84 # Transform new data using same parameters
85 new_data = [15 , 25 , 35]
86 transformed = scaler . transform ( new_data )
87 print ( f " Transformed new data : { transformed } " )
29
30
30
31
31
32
32
33
33
34
12 self . loaded_data = [
13 [1 , 2 , 3] ,
14 [4 , 5 , 6] ,
15 [7 , 8 , 9]
16 ]
17 self . source = filepath
18 return self . loaded_data
19
20 def from_json ( self , filepath ) :
21 " " " Load data from JSON file . " " "
22 print ( f " Loading JSON from { filepath } " )
23 # Simulated JSON loading
24 self . loaded_data = [
25 { ’ feature1 ’: 1 , ’ feature2 ’: 2} ,
26 { ’ feature1 ’: 3 , ’ feature2 ’: 4} ,
27 { ’ feature1 ’: 5 , ’ feature2 ’: 6}
28 ]
29 self . source = filepath
30 return self . loaded_data
31
32
33 class DataCleaner :
34 " " " Handle data cleaning operations . " " "
35
36 def __init__ ( self ) :
37 self . cleaning_log = []
38
39 def remove_nulls ( self , data ) :
40 " " " Remove rows with null values . " " "
41 if not data :
42 return data
43
44 # Handle both list of lists and list of dicts
45 if isinstance ( data [0] , dict ) :
46 cleaned = [ row for row in data if all ( v is not None for v in row .
values () ) ]
47 else :
48 cleaned = [ row for row in data if all ( v is not None for v in row ) ]
49
50 self . cleaning_log . append ( f " Removed nulls : { len ( data ) - len ( cleaned ) }
rows " )
51 return cleaned
52
53 def normalize_types ( self , data ) :
54 " " " Ensure consistent data types . " " "
55 if not data or isinstance ( data [0] , dict ) :
56 return data
57
58 # Convert all to float
59 cleaned = []
60 for row in data :
61 cleaned . append ([ float ( x ) if x is not None else 0.0 for x in row ])
62
63 self . cleaning_log . append ( " Normalized types to float " )
64 return cleaned
65
66 def get_log ( self ) :
67 " " " Return cleaning operation log . " " "
68 return self . cleaning_log . copy ()
69
70
71 class FeatureEngineer :
72 " " " Create and transform features . " " "
34
35
73
74 def __init__ ( self ) :
75 self . features_created = []
76
77 def add_interaction ( self , data , col1 , col2 ) :
78 " " " Add interaction feature . " " "
79 if isinstance ( data [0] , dict ) :
80 for row in data :
81 row [ f ’ interaction_ { col1 } _ { col2 } ’] = row [ col1 ] * row [ col2 ]
82 else :
83 for row in data :
84 row . append ( row [ col1 ] * row [ col2 ])
85
86 self . features_created . append ( f ’ interaction_ { col1 } _ { col2 } ’)
87 return data
88
89 def add_polynomial ( self , data , col , degree =2) :
90 " " " Add polynomial features . " " "
91 if isinstance ( data [0] , dict ) :
92 for d in range (2 , degree + 1) :
93 for row in data :
94 row [ f ’{ col }^{ d } ’] = row [ col ] ** d
95 else :
96 for d in range (2 , degree + 1) :
97 for i , row in enumerate ( data ) :
98 data [ i ] = row + [ row [ col ] ** d ]
99
100 self . features_created . append ( f ’ poly_ { col } _deg { degree } ’)
101 return data
102
103
104 class ModelTrainer :
105 " " " Train and evaluate models . " " "
106
107 def __init__ ( self , model ) :
108 self . model = model
109 self . training_history = []
110
111 def train ( self , X , y , validation_split =0.2) :
112 " " " Train model with validation . " " "
113 split_idx = int ( len ( X ) * (1 - validation_split ) )
114
115 X_train , X_val = X [: split_idx ] , X [ split_idx :]
116 y_train , y_val = y [: split_idx ] , y [ split_idx :]
117
118 # Train model
119 self . model . fit ( X_train , y_train )
120
121 # Validate
122 train_score = self . model . score ( X_train , y_train )
123 val_score = self . model . score ( X_val , y_val )
124
125 self . training_history . append ({
126 ’ train_score ’: train_score ,
127 ’ val_score ’: val_score ,
128 ’ samples ’: len ( X )
129 })
130
131 return self . model
132
133 def get_history ( self ) :
134 " " " Return training history . " " "
135 return self . training_history . copy ()
35
36
136
137
138 class D a t aSciencePipeline :
139 " " " Complete pipeline using composition . " " "
140
141 def __init__ ( self , name ) :
142 self . name = name
143 self . loader = DataLoader ()
144 self . cleaner = DataCleaner ()
145 self . engineer = FeatureEngineer ()
146 self . trainer = None
147 self . data = None
148 self . labels = None
149
150 def set_model ( self , model ) :
151 " " " Set the model to use . " " "
152 self . trainer = ModelTrainer ( model )
153 return self
154
155 def load_data ( self , source_type , filepath ) :
156 " " " Load data from source . " " "
157 if source_type == ’ csv ’:
158 self . data = self . loader . from_csv ( filepath )
159 elif source_type == ’ json ’:
160 self . data = self . loader . from_json ( filepath )
161 else :
162 raise ValueError ( f " Unknown source type : { source_type } " )
163
164 # Assume last column is target for simplicity
165 if self . data and not isinstance ( self . data [0] , dict ) :
166 self . labels = [ row [ -1] for row in self . data ]
167 self . data = [ row [: -1] for row in self . data ]
168
169 return self
170
171 def clean_data ( self ) :
172 " " " Apply cleaning operations . " " "
173 if self . data :
174 self . data = self . cleaner . remove_nulls ( self . data )
175 self . data = self . cleaner . normalize_types ( self . data )
176 return self
177
178 def engineer_features ( self ) :
179 " " " Create new features . " " "
180 if self . data and len ( self . data [0]) >= 2:
181 self . data = self . engineer . add_interaction ( self . data , 0 , 1)
182 self . data = self . engineer . add_polynomial ( self . data , 0 , degree =2)
183 return self
184
185 def run ( self ) :
186 " " " Execute the complete pipeline . " " "
187 if not self . trainer :
188 raise RuntimeError ( " No model set . Call set_model () first . " )
189
190 print ( f " \ nRunning pipeline : { self . name } " )
191 print ( " = " * 40)
192
193 # Train model
194 self . trainer . train ( self . data , self . labels )
195
196 # Report results
197 print ( f " Cleaning log : { self . cleaner . get_log () } " )
198 print ( f " Features created : { self . engineer . features_created } " )
36
37
37
38
38
39
39
40
169
170 result . _data = [
171 [ self . _data [ i ][ j ] * other . _data [ i ][ j ] for j in range ( len ( self .
columns ) ) ]
172 for i in range ( len ( self . _data ) )
173 ]
174
175 return result
176
177 # Comparison Magic Methods
178 def __eq__ ( self , other ) :
179 " " " Element - wise equality comparison . " " "
180 result = DataFrame ([] , self . columns . copy () )
181
182 if isinstance ( other , ( int , float , str ) ) :
183 result . _data = [[ x == other for x in row ] for row in self . _data ]
184
185 return result
186
187 def __gt__ ( self , other ) :
188 " " " Element - wise greater than comparison . " " "
189 result = DataFrame ([] , self . columns . copy () )
190
191 if isinstance ( other , ( int , float ) ) :
192 result . _data = [[ x > other for x in row ] for row in self . _data ]
193
194 return result
195
196 def __lt__ ( self , other ) :
197 " " " Element - wise less than comparison . " " "
198 result = DataFrame ([] , self . columns . copy () )
199
200 if isinstance ( other , ( int , float ) ) :
201 result . _data = [[ x < other for x in row ] for row in self . _data ]
202
203 return result
204
205 # String Representation
206 def __repr__ ( self ) :
207 " " " Detailed string representation . " " "
208 if not self . _data :
209 return " Empty DataFrame "
210
211 lines = []
212 lines . append ( f " DataFrame : { self . _shape [0]} rows { self . _shape [1]}
columns " )
213 lines . append ( f " Columns : { ’ , ’. join ( self . columns ) } " )
214 lines . append ( f " Types : { self . _dtypes } " )
215
216 # Show first few rows
217 lines . append ( " \ nFirst 5 rows : " )
218 header = " " . join ( f " { col : <10} " for col in self . columns [:5])
219 lines . append ( header )
220 lines . append ( " -" * len ( header ) )
221
222 for i , row in enumerate ( self . _data [:5]) :
223 row_str = " " . join ( f " { str ( x ) : <10} " [:10] for x in row [:5])
224 lines . append ( row_str )
225
226 if len ( self . _data ) > 5:
227 lines . append ( " ... " )
228
229 return " \ n " . join ( lines )
40
41
230
231 def __str__ ( self ) :
232 " " " Simple string representation . " " "
233 return f " DataFrame ({ self . _shape [0]} x { self . _shape [1]}) "
234
235 # Callable Magic Method
236 def __call__ ( self , * args , ** kwargs ) :
237 " " " Make DataFrame callable for common operations . " " "
238 if args and callable ( args [0]) :
239 # Apply function to all elements
240 func = args [0]
241 result = DataFrame ([] , self . columns . copy () )
242 result . _data = [[ func ( x ) for x in row ] for row in self . _data ]
243 return result
244 return self
245
246 # Context Manager Magic Methods
247 def __enter__ ( self ) :
248 " " " Enter context manager . " " "
249 print ( f " Entering DataFrame context : { self . _shape } " )
250 self . _backup = [ row . copy () for row in self . _data ]
251 return self
252
253 def __exit__ ( self , exc_type , exc_val , exc_tb ) :
254 " " " Exit context manager . " " "
255 if exc_type is not None :
256 print ( f " Error occurred : { exc_val } " )
257 print ( " Restoring backup ... " )
258 self . _data = self . _backup
259 else :
260 print ( " Exiting DataFrame context normally " )
261 del self . _backup
262 return False # Don ’t suppress exceptions
263
264 # Iterator Magic Methods
265 def __iter__ ( self ) :
266 " " " Iterate over rows . " " "
267 self . _iter_index = 0
268 return self
269
270 def __next__ ( self ) :
271 " " " Get next row . " " "
272 if self . _iter_index >= len ( self . _data ) :
273 raise StopIteration
274
275 row = dict ( zip ( self . columns , self . _data [ self . _iter_index ]) )
276 self . _iter_index += 1
277 return row
278
279 # Utility methods
280 @property
281 def shape ( self ) :
282 " " " Return DataFrame shape . " " "
283 return self . _shape
284
285 @property
286 def dtypes ( self ) :
287 " " " Return column data types . " " "
288 return self . _dtypes . copy ()
289
290 def head ( self , n =5) :
291 " " " Return first n rows . " " "
292 return DataFrame ( self . _data [: n ] , self . columns . copy () )
41
42
293
294 def tail ( self , n =5) :
295 " " " Return last n rows . " " "
296 return DataFrame ( self . _data [ - n :] , self . columns . copy () )
297
298 def describe ( self ) :
299 " " " Generate descriptive statistics . " " "
300 stats = {}
301 for i , col in enumerate ( self . columns ) :
302 col_data = [ row [ i ] for row in self . _data if isinstance ( row [ i ] , ( int ,
float ) ) ]
303 if col_data :
304 stats [ col ] = {
305 ’ count ’: len ( col_data ) ,
306 ’ mean ’: sum ( col_data ) / len ( col_data ) ,
307 ’ min ’: min ( col_data ) ,
308 ’ max ’: max ( col_data ) ,
309 ’ std ’: ( sum (( x - sum ( col_data ) / len ( col_data ) ) **2 for x in
col_data ) / len ( col_data ) ) **0.5
310 }
311 return stats
312
313 # Usage Example
314 print ( " = " * 50)
315 print ( " DEMONSTRATING MAGIC METHODS " )
316 print ( " = " * 50)
317
318 # Create DataFrame
319 data = [
320 [1 , 2 , 3] ,
321 [4 , 5 , 6] ,
322 [7 , 8 , 9] ,
323 [10 , 11 , 12]
324 ]
325 df = DataFrame ( data , columns =[ ’A ’ , ’B ’ , ’C ’ ])
326 print ( df )
327 print ()
328
329 # Container operations
330 print ( " Row 0: " , df [0])
331 print ( " Column A : " , df [ ’A ’ ])
332 print ( " First 2 rows :\ n " , df [:2])
333 print ()
334
335 # Assignment
336 df [ ’D ’] = [10 , 20 , 30 , 40]
337 print ( " After adding column D :\ n " , df . head (2) )
338 print ()
339
340 # Arithmetic operations
341 df2 = df + 10
342 print ( " df + 10:\ n " , df2 . head (2) )
343 print ()
344
345 # Comparisons
346 print ( " df > 5:\ n " , ( df > 5) . head (2) )
347 print ()
348
349 # Iteration
350 print ( " Iterating over rows : " )
351 for row in df . head (2) :
352 print ( row )
353 print ()
42
43
354
355 # Context manager
356 with df as d :
357 d [ ’E ’] = [100 , 200 , 300 , 400]
358 print ( " Inside context :\ n " , d . head (2) )
359 print ()
360
361 # Callable
362 squared = df ( lambda x : x **2)
363 print ( " Squared values :\ n " , squared . head (2) )
364 print ()
365
366 # Statistics
367 print ( " Descriptive statistics : " )
368 stats = df . describe ()
369 for col , stat in stats . items () :
370 print ( f " { col }: { stat } " )
43
44
41
42 @property
43 def values ( self ) :
44 " " " Get values ( read - only copy ) . " " "
45 return self . _values . copy ()
46
47 @property
48 def dates ( self ) :
49 " " " Get dates ( read - only copy ) . " " "
50 return self . _dates . copy ()
51
52 @property
53 def length ( self ) :
54 " " " Get series length . " " "
55 return len ( self . _values )
56
57 @property
58 def start_date ( self ) :
59 " " " Get first date . " " "
60 return self . _dates [0]
61
62 @property
63 def end_date ( self ) :
64 " " " Get last date . " " "
65 return self . _dates [ -1]
66
67 @property
68 def date_range ( self ) :
69 " " " Get date range as tuple . " " "
70 return ( self . start_date , self . end_date )
71
72 @property
73 def freq ( self ) :
74 " " " Infer frequency from dates . " " "
75 if self . _freq is None and len ( self . _dates ) > 1:
76 # Simple frequency inference
77 diffs = [ self . _dates [ i ] - self . _dates [i -1] for i in range (1 , len (
self . _dates ) ) ]
78 if all ( d == diffs [0] for d in diffs ) :
79 self . _freq = diffs [0]
80 else :
81 self . _freq = None
82 return self . _freq
Exception Handling
Theory
Exceptions are errors detected during program execution. Python provides a robust mechanism
to handle these errors gracefully so that programs do not crash unexpectedly. Exception handling
allows developers to detect errors and respond to them in a controlled manner.
try block: Encloses code that might raise an exception.
except block: Catches and handles specific exceptions.
else block: Executes if no exception occurs in the try block.
finally block: Always executes regardless of whether an exception occurred. It is ideal for
cleanup tasks such as closing files or releasing resources.
raise: Used to manually trigger an exception.
44
45
Example Programs
Basic try/except with else and finally
1 def divide (a , b ) :
2 try :
3 result = a / b
4 except ZeroDivisionError :
5 print ( " Error : Cannot divide by zero . " )
6 result = None
7 else :
8 print ( " Division successful . " )
9 finally :
10 print ( " Cleanup actions ( if any ) go here . " )
11 return result
12
13 print ( divide (10 , 2) ) # Output : 5.0
14 print ( divide (10 , 0) ) # Error : Cannot divide by zero . None
Sometimes programmers need to deliberately trigger an exception when invalid conditions occur.
Python provides the raise keyword to manually generate exceptions. This is useful for enforcing
constraints and validating input data.
1 def set_age ( age ) :
2 if age < 0:
3 raise ValueError ( " Age cannot be negative " )
4 print ( f " Age set to { age } " )
45
46
Debugging
Debugging is the process of identifying, analyzing, and fixing errors (bugs) in a program. It is an
essential skill in software development because even well-written programs may contain logical,
syntax, or runtime errors. Python provides several tools and techniques to assist in debugging.
Print Debugging: One of the simplest debugging techniques is inserting print() state-
ments in the code to track variable values and program execution flow.
Assertions: Assertions are used to check assumptions in the program. The syntax assert
condition, message verifies that the condition is true. If the condition evaluates to false,
Python raises an AssertionError.
Using pdb (Python Debugger): Python includes a built-in interactive debugger called
pdb. It allows developers to pause execution and inspect variables and program flow.
Example Programs
Using Assertions
46
47
1 import pdb
2
3 def buggy_function ( x ) :
4 y = x + 1
5 pdb . set_trace () # Execution pauses here
6 z = y * 2
7 return z
8
9 result = buggy_function (5)
10
11 # At the ( Pdb ) prompt , you can inspect variables :
12 # p y
13 # p x
14 # then continue with :
15 # c
1 def compute (a , b ) :
2 breakpoint () # Same as pdb . set_trace () but more convenient
3 return a / b
Logging
Logging is the preferred way to record runtime information in software applications. It is more
flexible and configurable than using simple print() statements. Logging helps developers mon-
itor program behavior, diagnose problems, and maintain a record of application events.
Logging Levels (in increasing severity):
• ERROR: A serious issue that prevents part of the program from working.
• CRITICAL: A severe error indicating the program may not continue running.
47
48
• Handler: Determines where the log messages are sent (e.g., console or file).
Best Practices:
Example Programs
1 import logging
2
3 logging . basicConfig ( level = logging . INFO ,
4 format = ’ %( asctime ) s - %( levelname ) s - %( message ) s ’)
5
6 def process ( data ) :
7 logging . info ( " Starting processing " )
8 try :
9 result = data / 2
10 except Exception as e :
11 logging . error ( f " Error : { e } " )
12 else :
13 logging . debug ( f " Result : { result } " ) # Won ’t appear ( level = INFO )
14 return result
15
16 process (10)
1 import logging
2
3 logging . basicConfig (
4 filename = ’ app . log ’ ,
5 level = logging . DEBUG ,
6 format = ’ %( asctime ) s - %( name ) s - %( levelname ) s - %( message ) s ’
7 )
8
9 def read_data ( filepath ) :
10 logging . info ( f " Attempting to read { filepath } " )
11 try :
12 with open ( filepath ) as f :
13 data = f . read ()
14 logging . debug ( f " Read { len ( data ) } characters " )
15 return data
16 except FileNotFoundError :
17 logging . error ( f " File { filepath } not found " )
48
49
1 import logging
2
3 # Create logger
4 logger = logging . getLogger ( ’ my_app ’)
5 logger . setLevel ( logging . DEBUG )
6
7 # Console handler ( only warnings and above )
8 c_handler = logging . StreamHandler ()
9 c_handler . setLevel ( logging . WARNING )
10 c_format = logging . Formatter ( ’ %( name ) s - %( levelname ) s - %( message ) s ’)
11 c_handler . setFormatter ( c_format )
12
13 # File handler ( all levels )
14 f_handler = logging . FileHandler ( ’ app . log ’)
15 f_handler . setLevel ( logging . DEBUG )
16 f_format = logging . Formatter ( ’ %( asctime ) s - %( name ) s - %( levelname ) s - %( message
) s ’)
17 f_handler . setFormatter ( f_format )
18
19 # Add handlers
20 logger . addHandler ( c_handler )
21 logger . addHandler ( f_handler )
22
23 # Test
24 logger . debug ( ’ Debug message ( file only ) ’)
25 logger . warning ( ’ Warning message ( both ) ’)
1 import logging
2 import pandas as pd
3
4 logging . basicConfig (
5 level = logging . INFO ,
6 filename = ’ pipeline . log ’ ,
7 format = ’ %( asctime ) s - %( message ) s ’
8 )
9
10 def load_and_clean ( csv_file ) :
11 logging . info ( f " Loading { csv_file } " )
12 df = pd . read_csv ( csv_file )
13 initial = len ( df )
14 df . dropna ( inplace = True )
15 dropped = initial - len ( df )
16 logging . info ( f " Dropped { dropped } rows with missing values " )
17 return df
49
50
Common Pitfalls:
• Catching exceptions too broadly (e.g., using a generic except)
• Forgetting to use finally for cleanup operations such as closing files
Debugging
Key Elements:
• print() statements for tracing program execution
• assert statements for validating assumptions
• pdb debugger and breakpoint() for interactive debugging
• IDE debugging tools such as breakpoints, step execution, and variable inspection
Common Pitfalls:
• Excessive reliance on print() debugging
• Leaving debugging statements in production code
Logging
Key Elements:
• Python logging module
• Logging levels: DEBUG, INFO, WARNING, ERROR, CRITICAL
• Handlers for directing logs to files or console
• Formatters for customizing log message structure
• Proper configuration using [Link]()
Common Pitfalls:
• Using print() instead of logging in real applications
• Not setting appropriate logging levels
• Misconfiguring handlers or logging formats
50
51
• import module name – imports the whole module. Access items using module [Link].
• from module name import item1, item2 – imports specific items into the current names-
pace.
• from module name import * – imports all public items (defined by all ). This ap-
proach should generally be avoided in production code.
• from [Link] import item – imports a specific item from a module inside a
package.
• When a module is executed directly using python [Link], name is set to " main ".
• When the module is imported, name is set to the module’s actual name.
51
52
• It may import submodules to make them accessible directly from the package.
1.7 all
all is a list of strings that defines which names are exported when using:
1 from module import *
If all is not defined, Python imports all names that do not start with an underscore.
Relative imports work only inside packages and not in top-level scripts.
2. Example Programs
File: [Link]
1 # mymath . py
2 def add (a , b ) :
3 return a + b
4
5 def subtract (a , b ) :
6 return a - b
7
8 PI = 3.14159
52
53
Directory Structure
1 mycalculator /
2 __init__ . py
3 basic . py
4 advanced /
5 __init__ . py
6 stats . py
7 trig . py
File: mycalculator/[Link]
1 def add (a , b ) :
2 return a + b
3
4 def subtract (a , b ) :
5 return a - b
File: mycalculator/advanced/[Link]
1 def mean ( data ) :
2 return sum ( data ) / len ( data )
1 __all__ = [ ’ add ’ , ’ PI ’]
2
3 def add (a , b ) :
4 return a + b
5
6 def subtract (a , b ) :
7 return a - b
8
9 PI = 3.14159
10 SECRET = " hidden "
53
54
• .py files
Common Pitfalls
Packages
Key Elements
• Subpackages
• all
Common Pitfalls
Import System
Key Elements
• [Link]
• Search order
• PYTHONPATH
54
55
Common Pitfalls
55
Chapter-2
Data Sources and APIs
Data sources refer to the origins, locations, or systems from which data is obtained for pro-
cessing, analysis, and interpretation. They encompass a wide spectrum of storage mechanisms,
formats, and delivery methods, each with distinct characteristics that influence how data must
be accessed, extracted, and prepared for analysis.
By Structure
• Structured Data Sources: Data with a predefined schema, typically organized in tables
with rows and columns. Examples include relational databases and CSV files.
• Semi-structured Data Sources: Data with a flexible structure that uses tags, markers,
or key-value pairs to organize information. Examples include JSON, XML, and HTML.
• Unstructured Data Sources: Data without a predefined structure. Examples include
text files, images, audio, and video.
By Access Method
• File-based Sources: Data stored in files that can be accessed locally or over a network,
such as CSV, Excel, or text files.
• Database Sources: Data stored in databases and accessed using query languages such
as SQL or through NoSQL systems.
• API Sources: Data obtained through web service endpoints using protocols such as
REST or SOAP.
• Stream Sources: Continuous data flows generated in real time, such as sensor data or
system logs.
By Storage Location
• Local Sources: Data stored on a local machine or device.
• Network Sources: Data accessed through network resources such as shared servers or
distributed systems.
• Cloud Sources: Data stored and managed through cloud-based services such as Amazon
S3, Google Cloud Storage, or Microsoft Azure.
1
2
By Data Origin
• Primary Sources: Data collected firsthand for a specific purpose, such as surveys,
experiments, or sensor measurements.
Historical Context
CSV emerged as a portable format when transferring data between incompatible database
systems. Its simplicity became its greatest strength—any system that can read and write text
can process CSV files.
Structural Components
• Records: Each line represents one record or row of data.
• Pipes (|)
• Semicolons (;)
Headers
The first row often contains column names, although this is not mandatory. When headers are
absent, columns are referenced using index positions (0, 1, 2, ...).
2
3
Quoting Mechanisms
• QUOTE ALL
• QUOTE MINIMAL
• QUOTE NONNUMERIC
• QUOTE NONE
Structural Components
Objects
Arrays
Advantages of JSON
• Human-readable format
• Lightweight structure
• Flexible schema
• Language independent
3
4
Excel Files
File Format Evolution
• XLS – legacy binary format
Unique Challenges
• Multiple sheets
• Formatting interference
Text Files
Conceptual Framework
Text files represent sequences of characters encoded in a specific encoding format.
• TSV
• Fixed-width files
• XML or HTML
Semi-structured Text
• Log files
• Configuration files
• JSON
Unstructured Text
• Emails
• Articles
• Transcripts
4
5
• INI/CFG
• Markdown
• reStructuredText
• LaTeX
1 import csv
2
3 # Basic CSV reading - returns rows as lists
4 with open ( ’ employees . csv ’ , ’r ’ , encoding = ’utf -8 ’) as file :
5 csv_reader = csv . reader ( file )
6
7 # Get the header row ( first row )
8 header = next ( csv_reader )
9 print ( f " Header : { header } " )
10
11 # Read and process data rows
12 print ( " \ nEmployee Data : " )
13 for row_num , row in enumerate ( csv_reader , start =2) :
14 print ( f " Row { row_num }: { row } " )
15 # Access by index : row [0] = name , row [1] = age , etc .
1 import csv
2
3 # Reading CSV with DictReader - rows as dictionaries
4 with open ( ’ employees . csv ’ , ’r ’ , encoding = ’utf -8 ’) as file :
5 csv_reader = csv . DictReader ( file )
6
7 # Access field names
8 print ( f " Columns : { csv_reader . fieldnames } " )
9
10 # Read and process data rows
11 for row in csv_reader :
12 print ( f " Name : { row [ ’ name ’]} , Age : { row [ ’ age ’]} , "
13 f " Department : { row [ ’ department ’]} " )
1 import csv
2
3 # Data to write : header row + data rows
4 data = [
5 [ ’ name ’ , ’ age ’ , ’ department ’ , ’ salary ’] , # Header
5
6
1 import csv
2
3 # Data as list of dictionaries
4 employees = [
5 { ’ name ’: ’ Eve Adams ’ , ’ age ’: 27 , ’ department ’: ’ Engineering ’ , ’ salary ’:
72000} ,
6 { ’ name ’: ’ Frank Miller ’ , ’ age ’: 38 , ’ department ’: ’ Sales ’ , ’ salary ’:
85000} ,
7 { ’ name ’: ’ Grace Lee ’ , ’ age ’: 31 , ’ department ’: ’ Marketing ’ , ’ salary ’:
68000}
8 ]
9
10 # Write using DictWriter
11 with open ( ’ employees_dict . csv ’ , ’w ’ , newline = ’ ’ , encoding = ’utf -8 ’) as file :
12 fieldnames = [ ’ name ’ , ’ age ’ , ’ department ’ , ’ salary ’]
13 writer = csv . DictWriter ( file , fieldnames = fieldnames )
14
15 # Write header
16 writer . writeheader ()
17
18 # Write all rows
19 writer . writerows ( employees )
Using pandas
1 import pandas as pd
2
3 # Simple CSV read
4 df = pd . read_csv ( ’ employees . csv ’)
5
6 print ( " DataFrame from CSV : " )
7 print ( df . head () ) # First 5 rows
8
9 print ( f " \ nShape : { df . shape } " )
10 print ( f " \ nData Types :\ n { df . dtypes } " )
6
7
1 import pandas as pd
2
3 # Reading with various options
4 df = pd . read_csv ( ’ employees . csv ’ ,
5 index_col =0 ,
6 header =0 ,
7 names =[ ’ name ’ , ’ age ’ , ’ dept ’ , ’ salary ’] ,
8 skiprows =[1 ,2] ,
9 nrows =100 ,
10 usecols =[ ’ name ’ , ’ salary ’] ,
11 dtype ={ ’ age ’: int , ’ salary ’: float } ,
12 parse_dates =[ ’ hire_date ’] ,
13 na_values =[ ’ NA ’ , ’ Missing ’] ,
14 encoding = ’utf -8 ’)
15
16 print ( df . head () )
1 import pandas as pd
2 import numpy as np
3
4 df = pd . read_csv ( ’ data_with_missing . csv ’ ,
5 na_values =[ ’ ’ , ’ NA ’ , ’ NULL ’ , ’ -999 ’] ,
6 keep_default_na = True )
7
8 print ( " Missing values per column : " )
9 print ( df . isnull () . sum () )
1 import pandas as pd
2
3 data = {
4 ’ name ’: [ ’ Alice ’ , ’ Bob ’ , ’ Charlie ’ , ’ Diana ’] ,
5 ’ age ’: [25 , 30 , 35 , 28] ,
6 ’ department ’: [ ’ HR ’ , ’ IT ’ , ’ IT ’ , ’ HR ’] ,
7 ’ salary ’: [50000 , 60000 , 65000 , 52000]
8 }
9
10 df = pd . DataFrame ( data )
11
12 df . to_csv ( ’ output_pandas . csv ’ ,
13 index = False ,
14 encoding = ’utf -8 ’)
7
8
7 float_format = ’ %.2 f ’ ,
8 encoding = ’utf -8 - sig ’ ,
9 date_format = ’%Y -% m -% d ’)
Table 1: Comparison between Python csv module and pandas for CSV operations
• Connection Objects: Represent the communication channel between Python and the
database, handling authentication, network communication, and session management.
• Cursor Objects: Execute SQL statements and fetch results, providing query execution
and result set iteration.
• Transactions: Groups of operations that execute as a single unit with commit or rollback
capabilities.
Relational Databases
Definition:
A relational database is a collection of tables containing data organized by relations between
data items. Relationships can be established between rows in different tables or between columns
inside a table.
Core Concepts:
Concept Description
Table Collection of related data organized in rows and columns
Row (Record) Single entry in a table representing one instance
Column (Field) Specific attribute of the data
Primary Key Uniquely identifies each row in a table
Foreign Key Establishes relationships between tables
Index Improves query performance
View Virtual table based on SELECT query
Schema Defines table structure and relationships
8
9
ACID Properties:
• Atomicity: Transactions are all-or-nothing; if any part fails, entire transaction rolls back.
SQL Categories:
• MySQL / MariaDB
• PostgreSQL
• Oracle
• Document Stores
• Key-Value Stores
• Column-Family Stores
9
10
• Graph Databases
Key Characteristics:
• Schema-less Design: Documents in the same collection can have different fields.
• Embedded Documents: Related data can be nested rather than split across tables.
BASE vs ACID:
ACID BASE
Strong consistency Weak consistency (eventual)
Isolation Availability first
Focus on commit Focus on continue
Pessimistic Optimistic
Complex Simple
1 import sqlite3
2
3 # Connect to database
4 conn = sqlite3 . connect ( ’ company . db ’)
5 print ( " Connected to database successfully " )
6
7 # Create a cursor
8 cursor = conn . cursor ()
9
10 # Create tables
11 cursor . execute ( ’ ’ ’
12 CREATE TABLE IF NOT EXISTS departments (
13 dept_id INTEGER PRIMARY KEY ,
14 dept_name TEXT NOT NULL ,
15 location TEXT
16 )
17 ’ ’ ’)
18
19 cursor . execute ( ’ ’ ’
20 CREATE TABLE IF NOT EXISTS employees (
21 emp_id INTEGER PRIMARY KEY ,
22 name TEXT NOT NULL ,
23 age INTEGER ,
24 salary REAL ,
25 dept_id INTEGER ,
10
11
26 hire_date TEXT ,
27 FOREIGN KEY ( dept_id ) REFERENCES departments ( dept_id )
28 )
29 ’ ’ ’)
30 conn . commit ()
31 print ( " Tables created successfully " )
32
33 # Insert data
34 departments = [
35 (1 , ’ Engineering ’ , ’ Building A ’) ,
36 (2 , ’ Marketing ’ , ’ Building B ’) ,
37 (3 , ’ Sales ’ , ’ Building C ’)
38 ]
39 cursor . executemany ( ’ INSERT INTO departments VALUES (? , ? , ?) ’ , departments )
40
41 employees = [
42 (101 , ’ Alice Brown ’ , 32 , 82000 , 1 , ’ 2022 -03 -15 ’) ,
43 (102 , ’ Bob Smith ’ , 45 , 95000 , 1 , ’ 2021 -06 -01 ’) ,
44 (103 , ’ Charlie Wilson ’ , 38 , 72000 , 2 , ’ 2023 -01 -10 ’) ,
45 (104 , ’ Diana Prince ’ , 29 , 68000 , 3 , ’ 2023 -08 -22 ’)
46 ]
47 cursor . executemany ( ’ INSERT INTO employees VALUES (? , ? , ? , ? , ? , ?) ’ , employees
)
48 conn . commit ()
49 print ( f " Inserted { cursor . rowcount } rows " )
B. Querying Data
1 import sqlite3
2
3 conn = sqlite3 . connect ( ’ company . db ’)
4 cursor = conn . cursor ()
5
6 # SELECT all employees
7 cursor . execute ( ’ SELECT * FROM employees ’)
8 all_employees = cursor . fetchall ()
9 print ( " All Employees : " )
10 for emp in all_employees :
11 print ( f " ID : { emp [0]} , Name : { emp [1]} , Salary : $ { emp [4]} " )
12
13 # SELECT with condition
14 cursor . execute ( ’ SELECT name , salary FROM employees WHERE dept_id = ? ’ , (1 ,) )
15 engineers = cursor . fetchall ()
16 print ( " \ nEngineering Department Employees : " )
17 for name , salary in engineers :
18 print ( f " { name }: $ { salary } " )
19
20 # SELECT with JOIN
21 cursor . execute ( ’ ’ ’
22 SELECT e . name , e . salary , d . dept_name , d . location
23 FROM employees e
24 JOIN departments d ON e . dept_id = d . dept_id
25 WHERE salary > ?
26 ’ ’ ’ , (70000 ,) )
27 high_earners = cursor . fetchall ()
28 print ( " \ nEmployees earning > $70 ,000: " )
29 for name , salary , dept , loc in high_earners :
30 print ( f " { name } ({ dept }) : $ { salary } " )
31
32 # Aggregation queries
33 cursor . execute ( ’ ’ ’
34 SELECT d . dept_name , COUNT (*) as emp_count , AVG ( e . salary ) as avg_salary
11
12
35 FROM employees e
36 JOIN departments d ON e . dept_id = d . dept_id
37 GROUP BY d . dept_name
38 ’ ’ ’)
39 stats = cursor . fetchall ()
40 print ( " \ nDepartment Statistics : " )
41 for dept , count , avg in stats :
42 print ( f " { dept }: { count } employees , Avg Salary : $ { avg :.2 f } " )
1 import sqlite3
2
3 conn = sqlite3 . connect ( ’ company . db ’)
4 cursor = conn . cursor ()
5
6 # UPDATE operation
7 cursor . execute ( ’ ’ ’
8 UPDATE employees
9 SET salary = salary * 1.10
10 WHERE dept_id = ?
11 ’ ’ ’ , (2 ,) )
12 conn . commit ()
13 print ( f " Updated { cursor . rowcount } rows in Marketing department " )
14
15 # DELETE operation
16 cursor . execute ( ’ DELETE FROM employees WHERE name = ? ’ , ( ’ Bob Smith ’ ,) )
17 conn . commit ()
18 print ( f " Deleted { cursor . rowcount } rows " )
19
20 # Parameterized queries
21 def u p d a t e _em pl oy ee _sa la ry ( emp_id , new_salary ) :
22 cursor . execute ( ’ ’ ’
23 UPDATE employees
24 SET salary = ?
25 WHERE emp_id = ?
26 ’ ’ ’ , ( new_salary , emp_id ) )
27 conn . commit ()
28 return cursor . rowcount
29
30 u p d a t e _ e m pl oy ee _sa la ry (101 , 85000)
31 print ( " Employee salary updated " )
32
33 conn . close ()
D. Transaction Management
1 import sqlite3
2
3 def trans fer_employee ( emp_id , new_dept_id ) :
4 conn = sqlite3 . connect ( ’ company . db ’)
5 cursor = conn . cursor ()
6
7 try :
8 # Check if employee exists
9 cursor . execute ( ’ SELECT name FROM employees WHERE emp_id = ? ’ , ( emp_id ,)
)
10 if not cursor . fetchone () :
11 raise Exception ( " Employee not found " )
12
12
13
1 import sqlite3
2 import pandas as pd
3
4 conn = sqlite3 . connect ( ’ company . db ’)
5
6 # Read SQL query into DataFrame
7 df_employees = pd . read_sql_query ( " SELECT * FROM employees " , conn )
8 print ( " Employees DataFrame : " )
9 print ( df_employees . head () )
10
11 # Read with conditions
12 df_high_salary = pd . read_sql_query ( ’ ’ ’
13 SELECT e . name , e . salary , d . dept_name
14 FROM employees e
15 JOIN departments d ON e . dept_id = d . dept_id
16 WHERE salary > 70000
17 ’ ’ ’ , conn )
18 print ( " \ nHigh Salary Employees : " )
19 print ( df_high_salary )
20
21 # Write DataFrame to SQL table
22 new_employees = pd . DataFrame ({
23 ’ name ’: [ ’ Eve Adams ’ , ’ Frank Miller ’] ,
24 ’ age ’: [27 , 41] ,
25 ’ salary ’: [72000 , 89000] ,
26 ’ dept_id ’: [1 , 3] ,
27 ’ hire_date ’: [ ’ 2024 -01 -15 ’ , ’ 2024 -02 -01 ’]
28 })
29 new_employees . to_sql ( ’ employees ’ , conn , if_exists = ’ append ’ , index = False )
30 print ( " \ nNew employees added to database " )
31
32 conn . close ()
13
14
MySQL/PostgreSQL Programs
A. MySQL Connection and Operations
14
15
B. PostgreSQL Connection
1 import psycopg2
2 from psycopg2 import sql
3
4 # Connect to PostgreSQL
5 conn = psycopg2 . connect (
6 host = " localhost " ,
7 database = " company_db " ,
8 user = " postgres " ,
9 password = " password " ,
10 port = " 5432 "
11 )
12
13 cursor = conn . cursor ()
14
15 # Create table with SERIAL for auto - increment
16 cursor . execute ( ’ ’ ’
17 CREATE TABLE IF NOT EXISTS customers (
18 customer_id SERIAL PRIMARY KEY ,
19 name VARCHAR (100) NOT NULL ,
20 email VARCHAR (100) UNIQUE ,
21 joined_date DATE DEFAULT CURRENT_DATE
22 )
23 ’ ’ ’)
24
25 # Insert using parameterized queries
26 cursor . execute ( ’ ’ ’
27 INSERT INTO customers ( name , email )
28 VALUES (% s , % s ) RETURNING customer_id
29 ’ ’ ’ , ( ’ John Doe ’ , ’ john@email . com ’) )
30 customer_id = cursor . fetchone () [0]
31 conn . commit ()
32 print ( f " Inserted customer with ID : { customer_id } " )
33
34 cursor . close ()
35 conn . close ()
15
16
B. Querying MongoDB
16
17
17
18
5 db = client [ ’ company_db ’]
6 orders = db [ ’ orders ’]
7
8 # Insert orders with nested documents
9 orders_data = [
10 {
11 ’ order_id ’: 1001 ,
12 ’ customer ’: { ’ name ’: ’ John Smith ’ , ’ email ’: ’ john@email . com ’ ,
13 ’ address ’: { ’ city ’: ’ New York ’ , ’ state ’: ’ NY ’}} ,
14 ’ items ’: [{ ’ product ’: ’ Laptop ’ , ’ price ’: 999.99 , ’ quantity ’: 1} ,
15 { ’ product ’: ’ Mouse ’ , ’ price ’: 24.99 , ’ quantity ’: 2}] ,
16 ’ total ’: 1049.97 ,
17 ’ date ’: datetime . now ()
18 },
19 {
20 ’ order_id ’: 1002 ,
21 ’ customer ’: { ’ name ’: ’ Jane Doe ’ , ’ email ’: ’ jane@email . com ’ ,
22 ’ address ’: { ’ city ’: ’ Boston ’ , ’ state ’: ’ MA ’}} ,
23 ’ items ’: [{ ’ product ’: ’ Desk Chair ’ , ’ price ’: 199.99 , ’ quantity ’: 1}] ,
24 ’ total ’: 199.99 ,
25 ’ date ’: datetime . now ()
26 }
27 ]
28 orders . insert_many ( orders_data )
29
30 # Query nested documents
31 ny_orders = orders . find ({ ’ customer . address . city ’: ’ New York ’ })
32 print ( " Orders from New York : " )
33 for order in ny_orders :
34 print ( f " Order { order [ ’ order_id ’]} - Customer : { order [ ’ customer ’][ ’ name ’]} " )
35
36 # Query array elements
37 large_orders = orders . find ({ ’ items ’: { ’ $elemMatch ’: { ’ price ’: { ’ $gt ’: 500}}}})
38 print ( " \ nOrders with items > $500 : " )
39 for order in large_orders :
40 print ( f " Order { order [ ’ order_id ’]} - Total : $ { order [ ’ total ’]} " )
41
42 # Aggregation pipeline
43 pipeline = [
44 { ’ $unwind ’: ’ $items ’} ,
45 { ’ $group ’: {
46 ’ _id ’: ’ $items . product ’ ,
47 ’ total_quantity ’: { ’ $sum ’: ’ $items . quantity ’} ,
48 ’ total_revenue ’: { ’ $sum ’: { ’ $multiply ’: [ ’ $items . price ’ , ’ $items .
quantity ’ ]}}
49 }} ,
50 { ’ $sort ’: { ’ total_revenue ’: -1}}
51 ]
52
53 results = orders . aggregate ( pipeline )
54 print ( " \ nProduct sales summary : " )
55 for result in results :
56 print ( f " Product : { result [ ’ _id ’]} " )
57 print ( f " Quantity : { result [ ’ total_quantity ’]} " )
58 print ( f " Revenue : $ { result [ ’ total_revenue ’]:.2 f } " )
59
60 client . close ()
1 import redis
2
18
19
3 # Connect to Redis
4 r = redis . Redis ( host = ’ localhost ’ , port =6379 , db =0 , decode_responses = True )
5
6 # Test connection
7 try :
8 r . ping ()
9 print ( " Connected to Redis " )
10 except redis . ConnectionError :
11 print ( " Could not connect to Redis " )
12
13 # Set key - value pairs
14 r . set ( ’ user :1001: name ’ , ’ Alice Brown ’)
15 r . set ( ’ user :1001: email ’ , ’ alice@email . com ’)
16 r . set ( ’ user :1001: age ’ , 32)
17
18 # Get values
19 name = r . get ( ’ user :1001: name ’)
20 email = r . get ( ’ user :1001: email ’)
21 print ( f " User : { name } , Email : { email } " )
22
23 # Set multiple keys at once
24 r . mset ({
25 ’ user :1002: name ’: ’ Bob Smith ’ ,
26 ’ user :1002: email ’: ’ bob@email . com ’ ,
27 ’ user :1002: age ’: 45
28 })
29
30 # Check if key exists
31 if r . exists ( ’ user :1001: name ’) :
32 print ( " User 1001 exists " )
33
34 # Set with expiration (10 seconds )
35 r . setex ( ’ temp : session ’ , 10 , ’ session_data ’)
36
37 # Working with lists
38 r . lpush ( ’ recent_searches :1001 ’ , ’ laptop ’ , ’ mouse ’ , ’ keyboard ’)
39 searches = r . lrange ( ’ recent_searches :1001 ’ , 0 , -1)
40 print ( f " Recent searches : { searches } " )
41
42 # Working with hashes
43 r . hset ( ’ user :1003 ’ , mapping ={
44 ’ name ’: ’ Charlie Wilson ’ ,
45 ’ email ’: ’ charlie@email . com ’ ,
46 ’ age ’: 38 ,
47 ’ department ’: ’ Engineering ’
48 })
49
50 # Get all fields from hash
51 user_data = r . hgetall ( ’ user :1003 ’)
52 print ( f " User 1003 data : { user_data } " )
53
54 # Get specific fields
55 user_name = r . hget ( ’ user :1003 ’ , ’ name ’)
56 print ( f " Name : { user_name } " )
57
58 # Delete keys
59 r . delete ( ’ temp : session ’)
60 print ( " Deleted temporary key " )
61
62 # Get all keys matching pattern
63 keys = r . keys ( ’ user :* ’)
64 print ( f " All user keys : { keys } " )
65
19
20
66 # Close connection
67 r . close ()
20
21
– SQLite: [Link]()
– MySQL/PostgreSQL: [Link]() / [Link]()
– MongoDB: MongoClient()
– Redis: [Link]()
• Insert:
• Query:
• Update:
21
22
– Redis: [Link]()
• Delete:
• With pandas:
REST APIs
Definition: REST (Representational State Transfer) is an architectural style for designing web
services. A RESTful API uses HTTP requests to GET, PUT, POST, and DELETE data. It is built
on the concept of resources, where each resource is identified by a URL.
Core Principles of REST:
• Stateless: Each request from client to server must contain all information needed to
understand the request. The server does not store any client context between requests.
This improves scalability because the server doesn’t need to maintain session state.
• Client-Server: Separation of concerns between the user interface (client) and data stor-
age (server), allowing them to evolve independently. Clients don’t need to know about
data storage, and servers don’t need to know about the user interface.
22
23
• Layered System: Intermediary servers (proxies, gateways, load balancers) can exist
between client and server without either knowing. This improves scalability and enables
security policies.
• Code on Demand (optional): Servers can temporarily extend client functionality by
transferring executable code (like JavaScript).
23
24
– 304 Not Modified – Resource hasn’t changed; client can use cached version
• HTTP Method: Indicates the desired operation: GET, POST, PUT, DELETE, etc.
• Request Body: Data sent with POST or PUT requests, usually in JSON or XML format
Response Components
• Status Code: Three-digit number indicating success or failure of the request
Authentication Methods
• API Keys: Simple token passed in headers, URL, or body. Easy to implement but less
secure. Commonly used for public APIs with basic access control. Example:
Headers: {"X-API-Key": "your api key here"}
24
25
• Bearer Tokens (OAuth 2.0): Token-based authentication where a token is issued after
authentication and included in subsequent requests. Tokens have limited lifetime and can
be scoped to specific permissions. Example:
Headers: {"Authorization": "Bearer eyJhbGciOiJIUzI1NiIs..."}
• JWT (JSON Web Tokens): Self-contained tokens that include claims (user infor-
mation, permissions) and a digital signature. The server can verify token authenticity
without storing session state.
SOAP APIs
Definition: SOAP (Simple Object Access Protocol) is a protocol specification for exchanging
structured information in web services. It uses XML for message format and typically relies on
HTTP or SMTP for transmission. SOAP has strict standards and formal contracts between
services.
Key Characteristics:
• Protocol Agnostic: Can run over HTTP, SMTP, TCP, JMS, not limited to web proto-
cols
• Language and Platform Independent: Any language on any platform can implement
SOAP clients and servers
• Strict Standards: Formal contracts and validation through WSDL ensure compatibility
• Extensible: WS-* specifications add capabilities like security, transactions, and reliable
messaging
<?xml version="1.0"?>
<soap:Envelope
xmlns:soap="[Link]
<soap:Header>
<!-- Optional header elements for metadata -->
<auth:Authentication xmlns:auth="[Link]
<auth:Username>user</auth:Username>
<auth:Password>pass</auth:Password>
</auth:Authentication>
</soap:Header>
<soap:Body>
<!-- Required body with request or response data -->
<m:GetPrice xmlns:m="[Link]
<m:Symbol>IBM</m:Symbol>
</m:GetPrice>
</soap:Body>
<soap:Fault>
<!-- Optional fault element for errors (only in responses) -->
25
26
<faultcode>soap:Server</faultcode>
<faultstring>Processing error</faultstring>
<detail>Error details here</detail>
</soap:Fault>
</soap:Envelope>
• Body (Required) Contains the actual request or response data, including method name
and parameters
• Fault (Optional) Provides error information returned when a request fails; only appears
in responses
• Messages: Abstract definitions of the data being exchanged between client and service
• Bindings: Protocol and data format details for each port type
26
27
Error Handling: Proper error handling distinguishes between different types of failures:
Exception Patterns:
• Use Specific Exceptions: Different error types should raise different exceptions
• Include Context: Exceptions should include URL, status code, and error details
• Retry Logic: Transient errors (timeouts, 5xx) should be retried with backoff
• Fail Fast: Validation errors should fail immediately without making request
27
28
1 import requests
2
3 # Send a GET request to a public API
4 response = requests . get ( " https :// jsonplaceholder . typicode . com / posts /1 " )
5
6 # Check status code
7 print ( f " Status Code : { response . status_code } " )
8
9 # Check if request was successful
10 if response . status_code == 200:
11 print ( " Request successful ! " )
12
13 # Response content as string
14 print ( f " Raw response ( first 100 chars ) : { response . text [:100]}... " )
15
16 # Parse JSON response
17 data = response . json ()
18 print ( f " \ nParsed data type : { type ( data ) } " )
19 print ( f " Title : { data [ ’ title ’]} " )
20 print ( f " Body : { data [ ’ body ’][:50]}... " )
21 else :
22 print ( f " Error : { response . status_code } " )
1 import requests
2
3 # Send GET request to API
4 response = requests . get ( " https :// jsonplaceholder . typicode . com / posts " )
5
6 # Basic response info
7 print ( f " Status Code : { response . status_code } " )
8 print ( f " Response URL : { response . url } " )
9
10 # View all response headers
11 print ( " \n - - - Response Headers ---" )
12 for key , value in response . headers . items () :
13 print ( f " { key }: { value } " )
14
15 # Check specific headers
16 content_type = response . headers . get ( ’ Content - Type ’)
17 print ( f " \ nContent Type : { content_type } " )
18 print ( f " Server : { response . headers . get ( ’ Server ’, ’ Not specified ’) } " )
1 import requests
2
3 # Send GET request to API
4 response = requests . get ( " https :// jsonplaceholder . typicode . com / posts /1 " )
5
6 # Method 1: Get as text ( raw string )
7 text_response = response . text
8 print ( f " Text response ( type : { type ( text_response ) }) " )
9 print ( f " First 100 chars : { text_response [:100]}... " )
10
11 # Method 2: Get as bytes ( for binary data )
12 bytes_response = response . content
28
29
1 import requests
2
3 # Method 1: Pass parameters as dictionary
4 params = {
5 ’ userId ’: 1 ,
6 ’ _limit ’: 3
7 }
8
9 response = requests . get (
10 " https :// jsonplaceholder . typicode . com / posts " ,
11 params = params
12 )
13
14 print ( f " URL called : { response . url } " ) # Shows full URL with parameters
15 posts = response . json ()
16
17 print ( f " \ nRetrieved { len ( posts ) } posts for user 1: " )
18 for i , post in enumerate ( posts , 1) :
19 print ( f " { i }. { post [ ’ title ’]} " )
20
21 # Method 2: Different endpoint with parameters
22 response2 = requests . get (
23 " https :// jsonplaceholder . typicode . com / comments " ,
24 params ={ ’ postId ’: 1 , ’ _limit ’: 2}
25 )
26 comments = response2 . json ()
27 print ( f " \ nComments for post 1: { len ( comments ) } " )
28 for comment in comments :
29 print ( f " - { comment [ ’ name ’]} " )
1 import requests
2
3 # Get a todo item
4 response = requests . get ( " https :// jsonplaceholder . typicode . com / todos /1 " )
5 todo = response . json ()
6
7 # Access nested data ( though this API is flat , pattern applies )
8 print ( " Todo Item : " )
9 print ( f " ID : { todo . get ( ’ id ’) } " )
10 print ( f " Title : { todo . get ( ’ title ’) } " )
11 print ( f " Completed : { todo . get ( ’ completed ’) } " )
12
13 # Get user details
14 user_response = requests . get ( f " https :// jsonplaceholder . typicode . com / users /{ todo
. get ( ’ userId ’) } " )
15 user = user_response . json ()
16
17 print ( f " \ nAssigned User : " )
29
30
POST Requests
Sending JSON Data
1 import requests
2
3 # Data to send ( as Python dictionary )
4 new_post = {
5 ’ title ’: ’ My New Post ’ ,
6 ’ body ’: ’ This is the content of my new post created via API . ’ ,
7 ’ userId ’: 1
8 }
9
10 # Send POST request with JSON data
11 response = requests . post (
12 " https :// jsonplaceholder . typicode . com / posts " ,
13 json = new_post # ’ json ’ parameter automatically sets Content - Type header
14 )
15
16 print ( f " Status Code : { response . status_code } " )
17
18 if response . status_code == 201: # 201 = Created
19 created_post = response . json ()
20 print ( f " Created post with ID : { created_post . get ( ’ id ’) } " )
21 print ( f " Title : { created_post . get ( ’ title ’) } " )
22 print ( f " Body : { created_post . get ( ’ body ’) } " )
1 import requests
2
3 # Form data ( like HTML form submission )
4 form_data = {
5 ’ username ’: ’ john_doe ’ ,
6 ’ email ’: ’ john@example . com ’ ,
7 ’ age ’: 30
8 }
9
10 # Send as form - encoded data using ’ data ’ parameter
11 response = requests . post (
12 " https :// httpbin . org / post " , # Test endpoint that echoes request
13 data = form_data
14 )
15
16 result = response . json ()
17 print ( " Form data received by server : " )
18 for key , value in result [ ’ form ’ ]. items () :
19 print ( f " { key }: { value } " )
20
21 # Note the Content - Type header
22 print ( f " \ nContent - Type sent : { result [ ’ headers ’]. get ( ’ Content - Type ’) } " )
23 # Should be : application /x - www - form - urlencoded
30
31
1 import requests
2
3 # Custom headers
4 headers = {
5 ’ User - Agent ’: ’ MyApplication /1.0 ’ ,
6 ’ Accept ’: ’ application / json ’ ,
7 ’X - Custom - Header ’: ’ custom - value -123 ’ ,
8 ’X - API - Version ’: ’2 ’
9 }
10
11 data = {
12 ’ name ’: ’ Test Product ’ ,
13 ’ price ’: 29.99 ,
14 ’ category ’: ’ electronics ’
15 }
16
17 response = requests . post (
18 " https :// httpbin . org / post " ,
19 json = data ,
20 headers = headers
21 )
22
23 result = response . json ()
24 print ( " Headers sent to server : " )
25 for key , value in result [ ’ headers ’ ]. items () :
26 print ( f " { key }: { value } " )
27
28 print ( " \ nData sent : " )
29 print ( f " { result [ ’ json ’]} " )
1 import requests
2
3 # PUT replaces the entire resource
4 updated_post = {
5 ’ id ’: 1 ,
6 ’ title ’: ’ Updated Title ’ ,
7 ’ body ’: ’ This post has been completely updated . ’ ,
8 ’ userId ’: 1
9 }
10
11 response = requests . put (
12 " https :// jsonplaceholder . typicode . com / posts /1 " ,
13 json = updated_post
14 )
15
16 print ( f " PUT Status : { response . status_code } " )
17 if response . status_code == 200:
18 result = response . json ()
19 print ( f " Updated title : { result [ ’ title ’]} " )
1 import requests
2
3 # PATCH updates only specified fields
4 partial_update = {
31
32
1 import requests
2
3 # PATCH only updates specified fields
4 partial_update = {
5 ’ title ’: ’ Partially Updated Title ’ # Only change the title
6 }
7
8 response = requests . patch (
9 " https :// jsonplaceholder . typicode . com / posts /1 " ,
10 json = partial_update
11 )
12
13 print ( f " PATCH Status : { response . status_code } " )
14 if response . status_code == 200:
15 result = response . json ()
16 print ( f " New title : { result [ ’ title ’]} " )
17 print ( f " Body unchanged : { result [ ’ body ’][:30]}... " )
DELETE Requests
1 import requests
2
3 # DELETE request
4 response = requests . delete ( " https :// jsonplaceholder . typicode . com / posts /1 " )
5
6 print ( f " DELETE Status : { response . status_code } " )
7
8 # 204 No Content indicates successful deletion with no response body
9 if response . status_code == 204:
10 print ( " Resource deleted successfully " )
11 elif response . status_code == 200:
12 print ( " Resource deleted , response body : " , response . json () )
13 elif response . status_code == 404:
14 print ( " Resource not found " )
Authentication Examples
API Key Authentication
1 import requests
2
3 # API key in headers ( common pattern )
4 headers = {
5 ’X - API - Key ’: ’ your_api_key_here ’ ,
32
33
Basic Authentication
1 import requests
2 from requests . auth import HTTPBasicAuth
3
4 # Method 1: Using HTTPBasicAuth helper
5 response = requests . get (
6 " https :// api . example . com / protected " ,
7 auth = HTTPBasicAuth ( ’ username ’ , ’ password ’)
8 )
9
10 # Method 2: Shortcut syntax ( recommended )
11 response = requests . get (
12 " https :// api . example . com / protected " ,
13 auth =( ’ username ’ , ’ password ’)
14 )
15
16 print ( f " Status : { response . status_code } " )
17 if response . status_code == 200:
18 print ( " Authentication successful " )
1 import requests
2
3 class APIClient :
4 " " " Simple API client with bearer token authentication " " "
5
6 def __init__ ( self , base_url , access_token ) :
7 self . base_url = base_url . rstrip ( ’/ ’)
8 self . access_token = access_token
9 self . session = requests . Session ()
10
11 # Set default headers for all requests
12 self . session . headers . update ({
13 ’ Authorization ’: f ’ Bearer { access_token } ’ ,
14 ’ Content - Type ’: ’ application / json ’ ,
15 ’ Accept ’: ’ application / json ’
16 })
17
18 def get ( self , endpoint , params = None ) :
19 " " " Make authenticated GET request " " "
20 url = f " { self . base_url }/{ endpoint . lstrip ( ’/ ’) } "
21 response = self . session . get ( url , params = params )
22 return response
23
33
34
Session Management
1 import requests
2
3 # Using Session for multiple requests
4 # Sessions maintain cookies and connection pooling
5 session = requests . Session ()
6
7 # Set default headers for all requests in this session
8 session . headers . update ({
9 ’ User - Agent ’: ’ MyApp /1.0 ’ ,
10 ’ Accept ’: ’ application / json ’
11 })
12
13 # Login once ( if authentication sets cookies )
14 login_data = { ’ username ’: ’ user ’ , ’ password ’: ’ pass ’}
15 login_response = session . post (
16 " https :// api . example . com / login " ,
17 json = login_data
18 )
19
20 # Session automatically maintains cookies
21 if login_response . status_code == 200:
22 print ( " Logged in successfully " )
23
24 # Subsequent requests use the same session
25 # No need to send credentials again
26 profile_response = session . get ( " https :// api . example . com / profile " )
27 d ash board_response = session . get ( " https :// api . example . com / dashboard " )
28
29 print ( f " Profile status : { profile_response . status_code } " )
30 print ( f " Dashboard status : { dashboard_response . status_code } " )
31
32 # Access protected resources
33 data_response = session . get (
34 " https :// api . example . com / data " ,
35 params ={ ’ limit ’: 10}
36 )
34
35
37
38 if data_response . status_code == 200:
39 data = data_response . json ()
40 print ( f " Retrieved { len ( data ) } items " )
41
42 # Close session when done
43 session . close ()
Error Handling
1 import requests
2 from requests . exceptions import RequestException , ConnectionError , HTTPError ,
Timeout
3
4 def safe_api_request ( url , timeout =10) :
5 " " " Make API request with comprehensive error handling " " "
6
7 try :
8 response = requests . get ( url , timeout = timeout )
9
10 # Raise exception for HTTP errors (4 xx , 5 xx )
11 response . raise_for_status ()
12
13 # Success - return parsed JSON
14 return response . json ()
15
16 except ConnectionError as e :
17 print ( f " Connection Error : Could not connect to server - { e } " )
18 print ( " Check your internet connection or if the server is available " )
19 return None
20
21 except Timeout as e :
22 print ( f " Timeout Error : Request timed out after { timeout } seconds " )
23 print ( " The server is taking too long to respond " )
24 return None
25
26 except HTTPError as e :
27 print ( f " HTTP Error : { response . status_code } " )
28
29 # Handle specific status codes
30 if response . status_code == 400:
31 print ( " Bad Request - Check your parameters " )
32 elif response . status_code == 401:
33 print ( " Unauthorized - Check your authentication " )
34 elif response . status_code == 403:
35 print ( " Forbidden - You don ’t have permission " )
36 elif response . status_code == 404:
37 print ( " Not Found - The resource doesn ’t exist " )
38 elif response . status_code == 429:
39 print ( " Rate Limited - Too many requests " )
40 retry_after = response . headers . get ( ’ Retry - After ’ , ’ unknown ’)
41 print ( f " Retry after { retry_after } seconds " )
42 elif response . status_code >= 500:
43 print ( " Server Error - The server had a problem " )
44 return None
45
46 except RequestException as e :
47 print ( f " General Request Error : { e } " )
48 return None
49
50 # Usage
51 data = safe_api_request ( " https :// jsonplaceholder . typicode . com / posts /1 " )
35
36
52 if data :
53 print ( f " Success ! Retrieved : { data [ ’ title ’]} " )
1 import requests
2 import time
3 import logging
4 from requests . exceptions import RequestException
5
6 # Set up logging
7 logging . basicConfig ( level = logging . INFO )
8 logger = logging . getLogger ( __name__ )
9
10 class RESTAPIClient :
11 """
12 Complete REST API Client with authentication , error handling ,
13 retry logic , and session management
14 """
15
16 def __init__ ( self , base_url , auth_token = None , timeout =30 , max_retries =3) :
17 self . base_url = base_url . rstrip ( ’/ ’)
18 self . timeout = timeout
19 self . max_retries = max_retries
20 self . session = requests . Session ()
21 self . session . headers . update ({
22 ’ User - Agent ’: ’ RESTAPIClient /1.0 ’ ,
23 ’ Accept ’: ’ application / json ’ ,
24 ’ Content - Type ’: ’ application / json ’
25 })
26 if auth_token :
27 self . session . headers . update ({ ’ Authorization ’: f ’ Bearer { auth_token }
’ })
28 logger . info ( f " API Client initialized for { base_url } " )
29
30 def _request ( self , method , endpoint , params = None , data = None , retry_count =0)
:
31 url = f " { self . base_url }/{ endpoint . lstrip ( ’/ ’) } "
32 try :
33 logger . debug ( f " Making { method } request to { url } " )
34 response = self . session . request (
35 method = method ,
36 url = url ,
37 params = params ,
38 json = data ,
39 timeout = self . timeout
40 )
41 if response . status_code == 429:
42 retry_after = int ( response . headers . get ( ’ Retry - After ’ , 5) )
43 logger . warning ( f " Rate limited . Waiting { retry_after } seconds " )
44 if retry_count < self . max_retries :
45 time . sleep ( retry_after )
46 return self . _request ( method , endpoint , params , data ,
retry_count + 1)
47 else :
48 logger . error ( " Max retries exceeded for rate limit " )
49 response . raise_for_status ()
50 response . raise_for_status ()
51 return response
52 except requests . exceptions . ConnectionError as e :
53 logger . error ( f " Connection error : { e } " )
54 if retry_count < self . max_retries :
36
37
55 wait = 2 ** retry_count
56 logger . info ( f " Retrying in { wait } seconds ... " )
57 time . sleep ( wait )
58 return self . _request ( method , endpoint , params , data ,
retry_count + 1)
59 raise
60 except requests . exceptions . Timeout as e :
61 logger . error ( f " Timeout error : { e } " )
62 if retry_count < self . max_retries :
63 wait = 2 ** retry_count
64 logger . info ( f " Retrying in { wait } seconds ... " )
65 time . sleep ( wait )
66 return self . _request ( method , endpoint , params , data ,
retry_count + 1)
67 raise
68 except requests . exceptions . HTTPError as e :
69 logger . error ( f " HTTP error { response . status_code }: { e } " )
70 raise
71 except Exception as e :
72 logger . error ( f " Unexpected error : { e } " )
73 raise
74
75 def get ( self , endpoint , params = None ) :
76 response = self . _request ( ’ GET ’ , endpoint , params = params )
77 return response . json () if response . content else None
78
79 def post ( self , endpoint , data = None ) :
80 response = self . _request ( ’ POST ’ , endpoint , data = data )
81 return response . json () if response . content else None
82
83 def put ( self , endpoint , data = None ) :
84 response = self . _request ( ’ PUT ’ , endpoint , data = data )
85 return response . json () if response . content else None
86
87 def patch ( self , endpoint , data = None ) :
88 response = self . _request ( ’ PATCH ’ , endpoint , data = data )
89 return response . json () if response . content else None
90
91 def delete ( self , endpoint ) :
92 response = self . _request ( ’ DELETE ’ , endpoint )
93 return response . status_code == 204
94
95 def close ( self ) :
96 self . session . close ()
97 logger . info ( " API Client closed " )
98
99 # Usage example
100 if __name__ == " __main__ " :
101 client = RESTAPIClient (
102 base_url = " https :// jsonplaceholder . typicode . com " ,
103 timeout =10 ,
104 max_retries =2
105 )
106 try :
107 posts = client . get ( " posts " , params ={ " _limit " : 3})
108 print ( f " Retrieved { len ( posts ) } posts " )
109 for post in posts :
110 print ( f " - { post [ ’ title ’]} " )
111 new_post = client . post ( " posts " , data ={
112 " title " : " API Test Post " ,
113 " body " : " Created via REST client " ,
114 " userId " : 1
115 })
37
38
38
39
39
40
• Following redirects
40
41
In this structure, the <html> tag contains all other tags, <head> contains metadata, and
<body> contains the visible content.
41
42
1 import requests
2 from bs4 import BeautifulSoup
1 import requests
2
3 # Make a request to a web page
4 response = requests . get ( ’ https :// example . com ’)
5
6 # Check the status code
7 print ( f " Status code : { response . status_code } " )
8
9 # A status code of 200 means the request was successful
10 if response . status_code == 200:
11 # Get the HTML content
12 html_content = response . text
13 print ( f " Retrieved { len ( html_content ) } bytes " )
14 else :
15 print ( f " Failed to retrieve page : { response . status_code } " )
1 import requests
2
3 # Some websites require a proper User - Agent header
4 headers = {
5 ’ User - Agent ’: ’ Mozilla /5.0 ( Windows NT 10.0; Win64 ; x64 ) AppleWebKit /537.36
’
6 }
7
8 response = requests . get ( ’ https :// example . com ’ , headers = headers )
9 print ( f " Status code : { response . status_code } " )
1 import requests
2 from requests . exceptions import RequestException
3
4 def fetch_page ( url ) :
5 " " " Safely fetch a web page " " "
6 try :
7 response = requests . get ( url , timeout =10)
8 response . raise_for_status () # Raise exception for 4 xx /5 xx status codes
9 return response . text
10 except RequestException as e :
11 print ( f " Error fetching page : { e } " )
12 return None
13
14 html = fetch_page ( ’ https :// example . com ’)
15 if html :
16 print ( f " Retrieved { len ( html ) } bytes " )
42
43
43
44
44
45
45
46
46
47
1 import requests
2 from bs4 import BeautifulSoup
3
4 # Step 1: Fetch the page
5 url = " http :// quotes . toscrape . com "
6 response = requests . get ( url )
7
8 if response . status_code == 200:
9 # Step 2: Parse the HTML
10 soup = BeautifulSoup ( response . text , ’ html . parser ’)
11
12 # Step 3: Find all quotes
13 quotes = soup . find_all ( ’ div ’ , class_ = ’ quote ’)
14
15 # Step 4: Extract data from each quote
16 for quote in quotes :
17 text = quote . find ( ’ span ’ , class_ = ’ text ’) . text
18 author = quote . find ( ’ small ’ , class_ = ’ author ’) . text
19
20 tags = quote . find_all ( ’a ’ , class_ = ’ tag ’)
21 tag_list = [ tag . text for tag in tags ]
22
23 print ( f " Quote : { text } " )
24 print ( f " Author : { author } " )
25 print ( f " Tags : { ’ , ’. join ( tag_list ) } " )
26 print ( " -" * 50)
27 else :
28 print ( f " Failed to retrieve page : { response . status_code } " )
1 import requests
2 from bs4 import BeautifulSoup
3 import csv
4
5 # Fetch and parse the page
6 url = " http :// quotes . toscrape . com "
7 response = requests . get ( url )
8 soup = BeautifulSoup ( response . text , ’ html . parser ’)
9
10 # Find all quotes
11 quotes = soup . find_all ( ’ div ’ , class_ = ’ quote ’)
12
13 # Prepare data for CSV
14 data = []
15 for quote in quotes :
16 text = quote . find ( ’ span ’ , class_ = ’ text ’) . text
17 author = quote . find ( ’ small ’ , class_ = ’ author ’) . text
18
19 tags = quote . find_all ( ’a ’ , class_ = ’ tag ’)
20 tags_str = ’ , ’. join ([ tag . text for tag in tags ])
21
22 data . append ([ text , author , tags_str ])
23
24 # Write to CSV
25 with open ( ’ quotes . csv ’ , ’w ’ , newline = ’ ’ , encoding = ’utf -8 ’) as file :
26 writer = csv . writer ( file )
47
48
1 import requests
2 from bs4 import BeautifulSoup
3 import time
4
5 base_url = " http :// quotes . toscrape . com / page /{}/ "
6 all_quotes = []
7 page = 1
8
9 while True :
10 print ( f " Scraping page { page }... " )
11 url = base_url . format ( page )
12 response = requests . get ( url )
13
14 if response . status_code != 200:
15 print ( f " Failed to retrieve page { page } " )
16 break
17
18 soup = BeautifulSoup ( response . text , ’ html . parser ’)
19 quotes = soup . find_all ( ’ div ’ , class_ = ’ quote ’)
20
21 if not quotes :
22 print ( f " No quotes found on page { page } , stopping . " )
23 break
24
25 for quote in quotes :
26 text = quote . find ( ’ span ’ , class_ = ’ text ’) . text
27 author = quote . find ( ’ small ’ , class_ = ’ author ’) . text
28 all_quotes . append ({ ’ text ’: text , ’ author ’: author })
29
30 print ( f " Found { len ( quotes ) } quotes on page { page } " )
31
32 # Be polite - don ’t hammer the server
33 time . sleep (2)
34 page += 1
35
36 print ( f " Total quotes scraped : { len ( all_quotes ) } " )
1 import requests
2 from bs4 import BeautifulSoup
3
4 # Some websites block requests without proper headers
5 headers = {
6 ’ User - Agent ’: ’ Mozilla /5.0 ( Windows NT 10.0; Win64 ; x64 ) AppleWebKit /537.36
( KHTML , like Gecko ) Chrome /91.0.4472.124 Safari /537.36 ’ ,
7 ’ Accept ’: ’ text / html , application / xhtml + xml , application / xml ; q =0.9 , image / webp
,*/*; q =0.8 ’ ,
8 ’ Accept - Language ’: ’en - US , en ; q =0.5 ’ ,
9 ’ Connection ’: ’ keep - alive ’ ,
10 }
11
12 url = " http :// quotes . toscrape . com "
48
49
SUMMARY
• [Link](): Fetch web pages Key Methods: get(), post(), status code, text
• Navigating: Move through the parse tree Key Methods: parent, children, next sibling
• Extracting: Get data from tags Key Methods: get text(), get(), [’attribute’]
• Searching: Find elements in HTML Key Methods: By tag, class, id, CSS selector
49
50
50
51
• Key Characteristics:
IMPORTANT PROGRAMS
2.1 Basic Generator Function
1 import sys
2
3 # List comprehension ( eager ) - loads all into memory
4 list_squares = [ x * x for x in range (1000000) ]
5 print ( f " List memory : { sys . getsizeof ( list_squares ) } bytes " )
6
7 # Generator expression ( lazy ) - produces on demand
8 gen_squares = ( x * x for x in range (1000000) )
9 print ( f " Generator memory : { sys . getsizeof ( gen_squares ) } bytes " )
10
11 # Using the generator
12 for i , square in enumerate ( gen_squares ) :
13 if i < 5:
14 print ( square , end = " " )
15 else :
16 break
51
52
1 import pandas as pd
2
3 # Read CSV file in chunks
4 chunk_size = 100000 # Number of rows per chunk
5 total_rows = 0
6 total_sum = 0
7
8 print ( f " Processing file in chunks of { chunk_size } rows ... " )
9
10 for chunk in pd . read_csv ( ’ large_file . csv ’ , chunksize = chunk_size ) :
11 # Process each chunk
12 rows_in_chunk = len ( chunk )
13 total_rows += rows_in_chunk
14 total_sum += chunk [ ’ value_column ’ ]. sum ()
15
16 print ( f " Processed chunk with { rows_in_chunk } rows . Total so far : {
total_rows } " )
17
18 average = total_sum / total_rows if total_rows > 0 else 0
19 print ( f " \ nFinal result : Average = { average :.2 f } " )
1 import pandas as pd
2
3 def pr oc ess_large_file ( filename , chunk_size =50000) :
4 """
52
53
1 import pandas as pd
2
3 # Read and filter in chunks
4 chunk_size = 100000
5 filtered_chunks = []
6
7 for chunk in pd . read_csv ( ’ transactions . csv ’ , chunksize = chunk_size ) :
8 # Filter each chunk
9 filtered = chunk [ chunk [ ’ amount ’] > 1000]
10 if len ( filtered ) > 0:
11 filtered_chunks . append ( filtered )
12 print ( f " Found { len ( filtered ) } rows with amount > 1000 in this chunk " )
13
14 # Combine only the filtered results
15 if filtered_chunks :
16 result = pd . concat ( filtered_chunks , ignore_index = True )
17 print ( f " \ nTotal filtered rows : { len ( result ) } " )
1 import pandas as pd
2
53
54
1 class FileChunkIterator :
2 """
3 Custom iterator that reads files in chunks
4 """
5 def __init__ ( self , filename , chunk_size =1024) :
6 self . filename = filename
7 self . chunk_size = chunk_size
8 self . file = None
9 self . eof = False
10
11 def __iter__ ( self ) :
12 return self
13
14 def __enter__ ( self ) :
15 self . file = open ( self . filename , ’ rb ’)
16 return self
17
18 def __exit__ ( self , * args ) :
19 if self . file :
20 self . file . close ()
21
22 def __next__ ( self ) :
23 if self . eof :
24 raise StopIteration
25
26 chunk = self . file . read ( self . chunk_size )
27 if not chunk :
28 self . eof = True
29 raise StopIteration
30
31 return chunk
32
33 # Usage
34 with FileChunkIterator ( ’ large_file . bin ’ , chunk_size =4096) as it :
35 for i , chunk in enumerate ( it ) :
36 print ( f " Chunk { i +1}: { len ( chunk ) } bytes " )
37 if i >= 4: # Stop after 5 chunks
38 break
1 import csv
2
54
55
3 class CSVChunkReader :
4 """
5 Read CSV file in chunks using iterator protocol
6 """
7 def __init__ ( self , filename , chunk_size =1000) :
8 self . filename = filename
9 self . chunk_size = chunk_size
10 self . file = None
11 self . reader = None
12 self . header = None
13
14 def __enter__ ( self ) :
15 self . file = open ( self . filename , ’r ’)
16 self . reader = csv . reader ( self . file )
17 self . header = next ( self . reader ) # Read header
18 return self
19
20 def __exit__ ( self , * args ) :
21 if self . file :
22 self . file . close ()
23
24 def __iter__ ( self ) :
25 return self
26
27 def __next__ ( self ) :
28 " " " Return next chunk of rows " " "
29 chunk = []
30 for i in range ( self . chunk_size ) :
31 try :
32 row = next ( self . reader )
33 chunk . append ( row )
34 except StopIteration :
35 break
36 if not chunk :
37 raise StopIteration
38 return chunk
39
40 # Usage
41 with CSVChunkReader ( ’ large_data . csv ’ , chunk_size =1000) as reader :
42 for chunk_num , chunk in enumerate ( reader , 1) :
43 print ( f " Chunk { chunk_num }: { len ( chunk ) } rows " )
44 if chunk_num >= 5:
45 break
1 def f i b on acci_generator () :
2 " " " Generate Fibonacci numbers infinitely " " "
3 a, b = 0, 1
4 while True :
5 yield a
6 a, b = b, a + b
7
8 # Get first 10 Fibonacci numbers
9 fib = f i b onacci_generator ()
10 first_ten = [ next ( fib ) for _ in range (10) ]
11 print ( f " First 10 Fibonacci : { first_ten } " )
12
13 # Get next 5
14 next_five = [ next ( fib ) for _ in range (5) ]
15 print ( f " Next 5: { next_five } " )
55
56
1 import pandas as pd
2
3 class LargeCSVProcessor :
4 """
5 Complete solution for processing large CSV files
6 """
7 def __init__ ( self , filename , chunk_size =50000) :
8 self . filename = filename
9 self . chunk_size = chunk_size
10 self . total_rows = 0
11 self . chunk_count = 0
12
13 def process ( self , operations ) :
14 """
15 Apply operations to each chunk and combine results
16 """
17 results = {}
18 for chunk in pd . read_csv ( self . filename , chunksize = self . chunk_size ) :
19 self . chunk_count += 1
20 self . total_rows += len ( chunk )
21 print ( f " Processing chunk { self . chunk_count } ({ len ( chunk ) } rows ) " )
22
23 # Apply each operation to this chunk
24 for op_name , op_func in operations . items () :
25 chunk_result = op_func ( chunk )
26
27 # Combine results
28 if op_name not in results :
29 results [ op_name ] = chunk_result
30 else :
31 if isinstance ( chunk_result , ( int , float ) ) :
32 results [ op_name ] += chunk_result
33 elif isinstance ( chunk_result , set ) :
34 results [ op_name ]. update ( chunk_result )
35
36 results [ ’ total_rows ’] = self . total_rows
37 results [ ’ chunks ’] = self . chunk_count
38 return results
39
40 # Define operations
41 def sum_amount ( chunk ) :
42 return chunk [ ’ amount ’ ]. sum ()
43
44 def avg_amount ( chunk ) :
45 return chunk [ ’ amount ’ ]. mean ()
56
57
46
47 def uniqu e_categories ( chunk ) :
48 return set ( chunk [ ’ category ’ ]. unique () )
49
50 # Usage
51 processor = LargeCSVProcessor ( ’ sales_data . csv ’ , chunk_size =100000)
52 operations = {
53 ’ total_amount ’: sum_amount ,
54 ’ unique_categories ’: unique_categories
55 }
56 results = processor . process ( operations )
57
58 print ( f " \ nProcessed { results [ ’ total_rows ’]: ,} rows in { results [ ’ chunks ’]}
chunks " )
59 print ( f " Total amount : $ { results [ ’ total_amount ’]: ,.2 f } " )
60 print ( f " Unique categories : { len ( results [ ’ unique_categories ’]) } " )
1 import json
2
3 def stream_json ( filename ) :
4 """
5 Stream JSON objects from a file containing
6 one JSON object per line
7 """
57
58
• Filtering Chunks — Selective data extraction; Use when filtering large datasets.
58
Chapter-3
Advanced Data Wrangling and Trans-
formation
This framework ensures consistent organization, making data easier to manipulate, visualize,
and model. Most Pandas operations are designed to work seamlessly with tidy data.
1
2
4. Data Cleaning
Data cleaning involves identifying and correcting errors, inconsistencies, and inaccuracies.
• Missing data
• Duplicate data
• Inconsistent formatting
• Outliers
• Invalid values
• Inconsistent categorizations
• Imputation: Fill missing values with mean, median, mode, or forward/backward fill
• String to datetime
• Categorical encoding
2
3
• Changing case
• Removing/replacing characters
5. Data Transformation
5.1 Filtering and Selection
• Select subsets of rows based on conditions
• Random sampling
• Applying functions
• Conditional assignments
• Remove duplicates
• Equal-frequency binning
3
4
• Window/rolling operations
6. Combining Datasets
6.1 Concatenation
Stack datasets vertically (add rows) or horizontally (add columns). Attention to indices is
important.
7. Reshaping Data
7.1 Wide vs. Long Format
• Wide: One row per subject, multiple columns
• Long: Multiple rows per subject, key column indicates measurement type
4
5
8. Group Operations
8.1 Split-Apply-Combine Strategy
• Split: Break data into groups
• Rolling windows
• Computational efficiency
5
6
10.3 Reproducibility
• Document transformation steps
• Reusable pipelines
• Tidy data framework: Variables as columns, observations as rows, each unit as a separate
table
• Data cleaning: Handle missing values, duplicates, inconsistent formatting, outliers, invalid
values
• left: keep all rows from left DataFrame, match from right
• right: keep all rows from right DataFrame, match from left
Key parameters:
• on: column(s) to join on (must exist in both)
6
7
[Link]() joins on index by default and can also join on a column using on.
Output:
7
8
Output:
Output:
Index A B
x 1 4
y 2 5
z 3 NaN
Reshaping DataFrames
Reshaping changes the layout of a DataFrame without altering the data itself.
• stack(): Compresses a level from the column index to the row index, creating a MultiIn-
dex Series.
• unstack(): Inverse of stack; pivots a level of the row index to the column index.
• melt(): Unpivots a DataFrame from wide format to long format; identifier columns re-
main.
• pivot(): Creates a reshaped DataFrame from long to wide format; requires unique in-
dex/column combinations. For duplicates, use pivot table().
8
9
Original DataFrame:
Index A B
X 1 3
Y 2 4
Stacked output:
Index A B
X 1 3
Y 2 4
Melted output:
9
10
Pivoted output:
id math science
1 85 92
2 90 88
3 78 81
date A B
2023-01-01 150 150
2023-01-02 NaN 175
1 import pandas as pd
2
3 # Create MultiIndex DataFrame
4 arrays = [[ ’A ’ , ’A ’ , ’B ’ , ’B ’] , [1 , 2 , 1 , 2]]
5 index = pd . MultiIndex . from_arrays ( arrays , names =[ ’ letter ’ , ’ number ’ ])
6 df = pd . DataFrame ({ ’ value ’: [10 , 20 , 30 , 40]} , index = index )
7 print ( df )
8
9 # Unstack the ’ number ’ level
10 unstacked = df . unstack ( level = ’ number ’)
11 print ( unstacked )
10
11
letter 1 2
A 10 20
B 30 40
Joining ([Link]):
Reshaping:
Common Pitfalls:
• Forgetting to specify how in merge may lead to default inner join (data loss).
11
12
• None in Python
Detection Methods:
Handling Strategies:
• Removal / Dropping: Use dropna() to remove rows or columns with missing values.
Suitable when missing data is minimal and random.
• Flagging: Create indicator columns to mark missing values. This preserves information
about the missingness pattern.
• Advanced Methods:
12
13
print("Original DataFrame:")
print(df)
13
14
print(df_all_missing.dropna(how=’all’))
print("Original DataFrame:")
print(df)
14
15
print([Link]())
Output:
Original DataFrame:
A B C D
0 1.0 NaN a 10
1 2.0 2.0 b 20
2 NaN 3.0 NaN 30
3 4.0 NaN d 40
4 5.0 5.0 e 50
Is null?
A B C D
0 False True False False
1 False False False False
2 True False True False
3 False True False False
4 False False False False
15
16
1 2.0 2.0 b 20
4 5.0 5.0 e 50
Output:
Drop rows with any missing:
A B C D
1 2.0 2.0 b 20
4 5.0 5.0 e 50
16
17
1 2.0 2.0 20
2 NaN 3.0 30
3 4.0 NaN 40
4 5.0 5.0 50
# Forward fill
df_ffill = [Link](method=’ffill’)
print("\nForward fill:")
print(df_ffill)
# Backward fill
df_bfill = [Link](method=’bfill’)
print("\nBackward fill:")
print(df_bfill)
Output:
Fill with 0:
A B C
0 1.0 10.0 100
1 2.0 0.0 200
2 0.0 30.0 300
3 4.0 0.0 400
17
18
Forward fill:
A B C
0 1.0 10.0 100
1 2.0 10.0 200
2 2.0 30.0 300
3 4.0 30.0 400
4 5.0 50.0 500
5 5.0 60.0 600
Backward fill:
A B C
0 1.0 10.0 100
1 2.0 30.0 200
2 4.0 30.0 300
3 4.0 50.0 400
4 5.0 50.0 500
5 NaN 60.0 600
4. Interpolation
df = [Link]({’value’:[1,2,[Link],[Link],5,6,[Link],8,9,10]})
18
19
# Linear
df_linear = [Link](method=’linear’)
print("\nLinear interpolation:")
print(df_linear)
# Polynomial order 2
df_poly = [Link](method=’polynomial’, order=2)
print("\nPolynomial interpolation:")
print(df_poly)
# Time-based
dates = pd.date_range(’2023-01-01’, periods=10)
df_time = [Link]({’value’:[1,2,[Link],[Link],5,6,[Link],8,9,10]}, index=dates)
df_time_interp = df_time.interpolate(method=’time’)
print("\nTime-based interpolation:")
print(df_time_interp)
Output:
Linear interpolation:
value
0 1.0
1 2.0
2 3.0
3 4.0
4 5.0
5 6.0
6 7.0
7 8.0
8 9.0
9 10.0
Polynomial interpolation:
value
0 1.0
1 2.0
2 3.0
3 4.0
4 5.0
5 6.0
6 7.0
7 8.0
8 9.0
9 10.0
Time-based interpolation:
value
2023-01-01 1.0
2023-01-02 2.0
2023-01-03 3.0
2023-01-04 4.0
2023-01-05 5.0
19
20
2023-01-06 6.0
2023-01-07 7.0
2023-01-08 8.0
2023-01-09 9.0
2023-01-10 10.0
# Create flags
df[’age_missing’] = df[’age’].isnull()
df[’income_missing’] = df[’income’].isnull()
Output:
# Analyze
missing_summary = df_raw.isnull().sum()
missing_percent = (df_raw.isnull().sum()/len(df_raw))*100
# Clean
df_clean = df_raw.dropna(subset=[’name’])
df_clean[’age’] = df_clean[’age’].fillna(df_clean[’age’].median())
df_clean[’salary_missing’] = df_clean[’salary’].isnull()
df_clean[’salary’] = df_clean[’salary’].fillna(df_clean[’salary’].mean())
df_clean[’department’] = df_clean[’department’].fillna(df_clean[’department’].mode()[0])
20
21
print(df_clean)
print(df_clean.isnull().sum())
Output:
id 0
name 0
age 0
salary 0
department 0
salary_missing 0
dtype: int64
• Ordinal: Categories with a natural order. Examples: Education level (High School ¡
Bachelor’s ¡ Master’s ¡ PhD), ratings (poor, average, good).
• Binary: Special case with only two categories. Examples: Yes/No, True/False, Male/Fe-
male.
Representation in pandas
• object dtype: String values
Common Operations
Frequency Analysis:
21
22
Encoding Methods:
• Label Encoding: Convert categories to integers (preserves order for ordinal variables).
Factorizing:
1 import pandas as pd
2 import numpy as np
3
4 # Create DataFrame with categorical columns
5 df = pd . DataFrame ({
6 ’ name ’: [ ’ Alice ’ , ’ Bob ’ , ’ Charlie ’ , ’ David ’ , ’ Eve ’] ,
7 ’ department ’: [ ’ HR ’ , ’ IT ’ , ’ IT ’ , ’ Finance ’ , ’ HR ’] ,
8 ’ rating ’: [ ’ Good ’ , ’ Excellent ’ , ’ Average ’ , ’ Good ’ , ’ Poor ’] ,
9 ’ gender ’: [ ’F ’ , ’M ’ , ’M ’ , ’M ’ , ’F ’] ,
10 ’ salary ’: [50000 , 80000 , 75000 , 90000 , 55000]
11 })
12
13 print ( df )
Output:
22
23
Output:
Output:
Column Categories
department Finance, HR, IT
rating Average, Excellent, Good, Poor
gender F, M
1 df = pd . DataFrame ({
2 ’ department ’: [ ’ HR ’ , ’ IT ’ , ’ IT ’ , ’ Finance ’ , ’ HR ’ , ’ IT ’ , ’ HR ’ , ’ Finance ’] ,
3 ’ rating ’: [ ’ Good ’ , ’ Excellent ’ , ’ Average ’ , ’ Good ’ , ’ Poor ’ , ’ Excellent ’ , ’
Good ’ , ’ Average ’]
4 })
5
6 # Department counts
7 print ( df [ ’ department ’ ]. value_counts () )
8
9 # Rating counts
10 print ( df [ ’ rating ’ ]. value_counts () )
11
12 # Rating counts normalized
13 print ( df [ ’ rating ’ ]. value_counts ( normalize = True ) )
14
15 # Cross - tabulation
16 print ( pd . crosstab ( df [ ’ department ’] , df [ ’ rating ’ ]) )
17
18 # Cross - tab with margins
19 print ( pd . crosstab ( df [ ’ department ’] , df [ ’ rating ’] , margins = True ) )
Output:
23
24
Rating Count
Department Count
Good 3
HR 3
Excellent 2
IT 3
Average 2
Finance 2
Poor 1
1 df = pd . DataFrame ({
2 ’ name ’: [ ’ Alice ’ , ’ Bob ’ , ’ Charlie ’ , ’ David ’ , ’ Eve ’] ,
3 ’ rating ’: [ ’ Good ’ , ’ Excellent ’ , ’ Average ’ , ’ Good ’ , ’ Poor ’]
4 })
5
6 # Manual mapping
7 rating_map = { ’ Poor ’:1 , ’ Average ’:2 , ’ Good ’:3 , ’ Excellent ’ :4}
8 df [ ’ rating_encoded ’] = df [ ’ rating ’ ]. map ( rating_map )
9
10 # Factorize
11 df [ ’ ratin g_factorized ’] , unique = pd . factorize ( df [ ’ rating ’ ])
12 print ( df )
13 print ( " Unique values : " , unique . tolist () )
Output:
d) One-Hot Encoding
1 df = pd . DataFrame ({
2 ’ id ’: [1 ,2 ,3 ,4 ,5] ,
3 ’ department ’: [ ’ HR ’ , ’ IT ’ , ’ IT ’ , ’ Finance ’ , ’ HR ’] ,
4 ’ gender ’: [ ’F ’ , ’M ’ , ’M ’ , ’M ’ , ’F ’]
24
25
5 })
6
7 df_encoded = pd . get_dummies ( df , columns =[ ’ department ’ , ’ gender ’ ])
8 df_dummy = pd . get_dummies ( df , columns =[ ’ department ’ , ’ gender ’] , drop_first = True )
9 df_prefix = pd . get_dummies ( df , columns =[ ’ department ’] , prefix = ’ dept ’)
Common Operations:
1. Date/Time Parsing
• [Link] datetime(): Convert strings to datetime objects.
• Partial string indexing: e.g., df[’2023-01’] selects all January 2023 data.
3. Resampling
• Change frequency of time series: upsampling or downsampling.
25
26
5. Rolling Windows
• rolling(): Apply functions over a sliding time window.
1 import pandas as pd
2 import numpy as np
3 from datetime import datetime
4
5 # Create Timestamp from string
6 ts1 = pd . Timestamp ( ’ 2023 -01 -15 ’)
7 ts2 = pd . Timestamp ( ’ 2023 -12 -31 ’)
8
9 # Create from datetime object
10 dt = datetime (2023 , 6 , 15 , 14 , 30)
11 ts3 = pd . Timestamp ( dt )
12
13 # Create date range
14 dates = pd . date_range ( start = ’ 2023 -01 -01 ’ , end = ’ 2023 -01 -10 ’ , freq = ’D ’)
15
16 # Create with specific frequency
17 month_starts = pd . date_range ( start = ’ 2023 -01 -01 ’ , periods =5 , freq = ’ MS ’) # Month
start
18 business_days = pd . date_range ( start = ’ 2023 -01 -01 ’ , periods =10 , freq = ’B ’) #
Business days
19
20 # Create DataFrame with datetime index
21 df = pd . DataFrame ({ ’ value ’: np . random . randn ( len ( dates ) ) } , index = dates )
26
27
7 })
8
9 # Parse different formats
10 df [ ’ date1 ’] = pd . to_datetime ( df [ ’ date_str1 ’ ])
11 df [ ’ date2 ’] = pd . to_datetime ( df [ ’ date_str2 ’] , format = ’% d /% m /% Y ’)
12 df [ ’ date3 ’] = pd . to_datetime ( df [ ’ date_str3 ’ ])
13
14 # Handle errors
15 df_bad = pd . DataFrame ({
16 ’ date ’: [ ’ 2023 -01 -15 ’ , ’ invalid_date ’ , ’ 2023 -03 -25 ’] ,
17 ’ value ’: [100 , 200 , 300]
18 })
19 df_bad [ ’ date_coerce ’] = pd . to_datetime ( df_bad [ ’ date ’] , errors = ’ coerce ’)
1 import pandas as pd
2 import numpy as np
3
4 # Create high - frequency data ( hourly )
5 dates = pd . date_range ( ’ 2023 -01 -01 00:00 ’ , periods =24*7 , freq = ’H ’) # One week of
hourly data
6 df = pd . DataFrame ({ ’ value ’: np . random . randn ( len ( dates ) ) . cumsum () } , index = dates )
7
8 # Downsampling : Hourly to daily
9 daily_mean = df . resample ( ’D ’) . mean ()
10 daily_sum = df . resample ( ’D ’) . sum ()
11 daily_max = df . resample ( ’D ’) . max ()
12
13 # Downsampling with multiple aggregations
14 daily_stats = df . resample ( ’D ’) . agg ([ ’ mean ’ , ’ std ’ , ’ min ’ , ’ max ’ ])
27
28
15
16 # Create lower - frequency data ( daily )
17 dates_daily = pd . date_range ( ’ 2023 -01 -01 ’ , periods =30 , freq = ’D ’)
18 df_daily = pd . DataFrame ({ ’ value ’: np . random . randn (30) . cumsum () } , index =
dates_daily )
19
20 # Upsampling : Daily to hourly ( interpolation )
21 h o u rl y _ in t erpolated = df_daily . resample ( ’H ’) . interpolate ( method = ’ linear ’)
22
23 # Upsampling with forward fill
24 hourly_ffill = df_daily . resample ( ’H ’) . ffill ()
25
26 # Custom resampling functions
27 def custom_range ( x ) :
28 return x . max () - x . min ()
29
30 daily_range = df . resample ( ’D ’) . apply ( custom_range )
1 import pandas as pd
2 import numpy as np
3
4 # Create simple time series
5 dates = pd . date_range ( ’ 2023 -01 -01 ’ , periods =10 , freq = ’D ’)
6 df = pd . DataFrame ({ ’ value ’: [10 , 12 , 15 , 14 , 18 , 20 , 22 , 21 , 25 , 28]} , index =
dates )
7
8 # Shift forward ( lag ) and backward ( lead )
9 df [ ’ lag_1 ’] = df [ ’ value ’ ]. shift (1)
10 df [ ’ lag_2 ’] = df [ ’ value ’ ]. shift (2)
11 df [ ’ lag_ -1 ’] = df [ ’ value ’ ]. shift ( -1)
12
13 # Create features for time series prediction
14 df [ ’ value_lag1 ’] = df [ ’ value ’ ]. shift (1)
15 df [ ’ value_lag2 ’] = df [ ’ value ’ ]. shift (2)
16 df [ ’ value_lag3 ’] = df [ ’ value ’ ]. shift (3)
17 df [ ’ value_lead1 ’] = df [ ’ value ’ ]. shift ( -1)
18
19 # Calculate differences and percentage change
20 df [ ’ diff_1 ’] = df [ ’ value ’ ]. diff (1)
21 df [ ’ diff_2 ’] = df [ ’ value ’ ]. diff (2)
22 df [ ’ pct_change ’] = df [ ’ value ’ ]. pct_change ()
1 import pandas as pd
2 import numpy as np
3
4 # Create time series data
5 dates = pd . date_range ( ’ 2023 -01 -01 ’ , periods =30 , freq = ’D ’)
6 df = pd . DataFrame ({ ’ value ’: np . random . randn (30) . cumsum () } , index = dates )
7
8 print ( " Original data : " )
9 print ( df . head (10) )
10
11 # Rolling mean ( moving average )
12 df [ ’ rolling_mean_3 ’] = df [ ’ value ’ ]. rolling ( window =3) . mean ()
13 df [ ’ rolling_mean_7 ’] = df [ ’ value ’ ]. rolling ( window =7) . mean ()
14
15 print ( " \ nWith rolling means : " )
28
29
1 import pandas as pd
2 import numpy as np
3
4 # Create time series without timezone
5 dates = pd . date_range ( ’ 2023 -01 -01 09:00 ’ , periods =5 , freq = ’H ’)
6 df = pd . DataFrame ({ ’ value ’: [100 , 110 , 120 , 130 , 140]} , index = dates )
7
8 print ( " Original ( naive ) : " )
9 print ( df )
10
11 # Localize to a timezone ( assign timezone )
12 df_tz = df . copy ()
13 df_tz . index = df_tz . index . tz_localize ( ’ UTC ’)
14 print ( " \ nLocalized to UTC : " )
15 print ( df_tz )
16
17 # Convert to another timezone
18 df_ny = df_tz . tz_convert ( ’ America / New_York ’)
19 print ( " \ nConverted to New York time : " )
20 print ( df_ny )
21
22 df_london = df_tz . tz_convert ( ’ Europe / London ’)
23 print ( " \ nConverted to London time : " )
24 print ( df_london )
25
26 # Create with timezone directly
27 dates_tz = pd . date_range ( ’ 2023 -01 -01 09:00 ’ , periods =5 , freq = ’H ’ , tz = ’ Asia / Tokyo
’)
28 df_tokyo = pd . DataFrame ({ ’ value ’: [200 , 210 , 220 , 230 , 240]} , index = dates_tz )
29 print ( " \ nCreated with Tokyo timezone : " )
30 print ( df_tokyo )
29
30
1 import pandas as pd
2 import numpy as np
3
4 # Create time series
5 dates = pd . date_range ( ’ 2023 -01 -01 ’ , periods =365 , freq = ’D ’)
6 df = pd . DataFrame ({ ’ value ’: np . random . randn (365) . cumsum () } , index = dates )
7
8 # Extract various components
9 df [ ’ year ’] = df . index . year
10 df [ ’ month ’] = df . index . month
11 df [ ’ day ’] = df . index . day
12 df [ ’ dayofweek ’] = df . index . dayofweek # Monday =0 , Sunday =6
13 df [ ’ day_name ’] = df . index . day_name ()
14 df [ ’ month_name ’] = df . index . month_name ()
15 df [ ’ quarter ’] = df . index . quarter
16 df [ ’ week ’] = df . index . isocalendar () . week
17 df [ ’ dayofyear ’] = df . index . dayofyear
18 df [ ’ is_month_end ’] = df . index . is_month_end
19 df [ ’ is_month_start ’] = df . index . is_month_start
20 df [ ’ is_quarter_end ’] = df . index . is_quarter_end
21
22 print ( " Date components ( first 10 rows ) : " )
23 print ( df . head (10) )
24
25 # Group by date components
26 monthly_avg = df . groupby ( ’ month ’) [ ’ value ’ ]. mean ()
27 print ( " \ nMonthly averages : " )
28 print ( monthly_avg )
29
30 # Count by day of week
31 dow_counts = df . groupby ( ’ day_name ’) . size ()
32 print ( " \ nCount by day of week : " )
33 print ( dow_counts )
1 import pandas as pd
2 import numpy as np
3 from datetime import datetime , timedelta
4
5 # Generate sample sales data
6 np . random . seed (42)
7 dates = pd . date_range ( ’ 2023 -01 -01 ’ , ’ 2023 -12 -31 ’ , freq = ’D ’)
8 df = pd . DataFrame ({
9 ’ date ’: dates ,
10 ’ sales ’: np . random . poisson ( lam =100 , size = len ( dates ) ) +
11 np . sin ( np . arange ( len ( dates ) ) * 2 * np . pi / 365) * 20 + # Seasonal
12 np . random . randn ( len ( dates ) ) * 10 # Noise
13 })
14
15 print ( " Raw sales data : " )
16 print ( df . head () )
17
18 # Step 1: Set datetime as index
19 df [ ’ date ’] = pd . to_datetime ( df [ ’ date ’ ])
20 df . set_index ( ’ date ’ , inplace = True )
21
22 # Step 2: Check for missing dates
23 date_range = pd . date_range ( start = df . index . min () , end = df . index . max () , freq = ’D ’)
24 if len ( df ) < len ( date_range ) :
30
31
1. Missing Data
• Detection: isnull(), notnull(), info(), isnull().sum()
31
32
2. Categorical Data
• Types:
• Encoding:
3. Time-Series Data
• Creation: to datetime(), date range(), Timestamp
• Shifting: shift() for lags/leads, diff() for differences, pct change() for percentage
change
• Rolling: rolling() for window statistics (mean, std, min, max, sum, custom)
32
Feature Transformation, Scaling, and
Encoding
1. Feature Transformation
1.1 Theory
Feature transformation involves applying mathematical functions to modify the distribution or
relationships of features. It is often used to:
• Make data more normally distributed (many statistical models assume normality)
• Stabilize variance
• Linearize relationships
• Reduce skewness
• Transformations should be applied to training data, then the same parameters used on
test/validation data
33
34
3. Reciprocal Transformation
Formula:
1
y=
x
Purpose: Useful for rates or ratios; strongly reduces right skewness, sensitive to small
values.
Example: x = [1, 2, 5, 10, 20]
1
= [1.0000, 0.5000, 0.2000, 0.1000, 0.0500]
x
4. Box–Cox Transformation
Formula:
λ
x − 1 , λ ̸= 0
y(λ) = λ
log(x), λ = 0
x2 − 1
y= = [0.0, 1.5, 4.0, 7.5, 12.0]
2
5. Yeo–Johnson Transformation
Formula:
λ
(x + 1) − 1 ,
x ≥ 0, λ ̸= 0
λ
log(x + 1), x ≥ 0, λ = 0
y(λ) =
(−x + 1)2−λ − 1
− , x < 0, λ ̸= 2
2−λ
− log(−x + 1), x < 0, λ = 2
34
35
6. Power Transformation
Formula:
y = xλ
Summary of Transformations
a) Log Transformation
1 import pandas as pd
2 import numpy as np
3 import matplotlib . pyplot as plt
4
5 # Create skewed data
6 np . random . seed (42)
7 data = np . random . exponential ( scale =2 , size =1000)
8 df = pd . DataFrame ({ ’ original ’: data })
9
10 # Apply log transformation
11 df [ ’ log ’] = np . log ( df [ ’ original ’ ])
12 df [ ’ log1p ’] = np . log1p ( df [ ’ original ’ ]) # handles zeros
13
14 # Compare skewness
15 print ( " Original - Skewness : " , df [ ’ original ’ ]. skew () )
16 print ( " Log - Skewness : " , df [ ’ log ’ ]. skew () )
17 print ( " Log1p - Skewness : " , df [ ’ log1p ’ ]. skew () )
18
19 # Optional visualization
20 # df . hist ( bins =50 , figsize =(10 ,4) )
21 # plt . show ()
Listing 1: Log Transformation on Skewed Data
35
36
1 import pandas as pd
2 import numpy as np
3
4 # Poisson count data
5 np . random . seed (42)
6 counts = np . random . poisson ( lam =5 , size =1000)
7 df = pd . DataFrame ({ ’ counts ’: counts })
8
9 # Square root transformation
10 df [ ’ sqrt ’] = np . sqrt ( df [ ’ counts ’ ])
11
12 print ( " Original - Skewness : " , df [ ’ counts ’ ]. skew () )
13 print ( " Sqrt - Skewness : " , df [ ’ sqrt ’ ]. skew () )
Listing 2: Square Root Transformation for Count Data
c) Box-Cox Transformation
1 import pandas as pd
2 import numpy as np
3 from scipy import stats
4
5 # Generate right - skewed data
6 np . random . seed (42)
7 data = np . random . gamma ( shape =2 , scale =2 , size =1000)
8 df = pd . DataFrame ({ ’ original ’: data })
9
10 # Box - Cox transformation ( add 1 if zeros present )
11 df [ ’ boxcox ’] , fitted_lambda = stats . boxcox ( df [ ’ original ’] + 1)
12
13 print ( f " Optimal lambda : { fitted_lambda :.4 f } " )
14 print ( " Original - Skewness : " , df [ ’ original ’ ]. skew () )
15 print ( " Box - Cox - Skewness : " , df [ ’ boxcox ’ ]. skew () )
Listing 3: Box-Cox Transformation
1 import pandas as pd
2 import numpy as np
3 from sklearn . preprocessing import PowerTransformer
4
5 # Data with negative values
6 np . random . seed (42)
7 data = np . random . randn (1000) * 2 + 1
8 df = pd . DataFrame ({ ’ original ’: data })
9
10 # Yeo - Johnson via PowerTransformer
11 pt = PowerTransformer ( method = ’yeo - johnson ’)
12 df [ ’ yeojohnson ’] = pt . fit_transform ( df [[ ’ original ’ ]])
13
14 print ( " Original - Skewness : " , df [ ’ original ’ ]. skew () )
15 print ( " Yeo - Johnson - Skewness : " , df [ ’ yeojohnson ’ ]. skew () )
16 print ( " Lambda used : " , pt . lambdas_ [0])
Listing 4: Yeo-Johnson Transformation using scikit-learn
36
37
1 import pandas as pd
2 import numpy as np
3 from sklearn . model_selection import train_test_split
4 from sklearn . preprocessing import PowerTransformer
5 from sklearn . linear_model import LinearRegression
6 from sklearn . pipeline import make_pipeline
7 from sklearn . metrics import r2_score
8
9 # Generate synthetic data
10 np . random . seed (42)
11 X = np . random . exponential ( size =(1000 , 1) )
12 y = 2 * np . log ( X ) . ravel () + np . random . randn (1000) * 0.5
13
14 X_train , X_test , y_train , y_test = train_test_split (X , y , test_size =0.2)
15
16 # Pipeline : transform then model
17 pipeline = make_pipeline (
18 PowerTransformer ( method = ’box - cox ’) , # automatically finds lambda
19 LinearRegression ()
20 )
21 pipeline . fit ( X_train , y_train )
22 y_pred = pipeline . predict ( X_test )
23 print ( f " R score : { r2_score ( y_test , y_pred ) :.4 f } " )
Listing 5: Feature Transformation in ML Pipeline
Feature Scaling
Theory
Feature scaling adjusts the range or distribution of numerical features. It is essential for algo-
rithms that rely on distance measures, such as k-NN, SVM, or PCA, and for methods that use
gradient descent, like neural networks and linear models with regularization.
• Robust Scaling: Center data by the median and scale according to the interquartile
range (IQR):
x − median
x′ =
IQR
Robust to outliers and effective when data contains extreme values.
37
38
Important Considerations
• Always fit the scaler on the training data only, then apply the transformation to validation
or test sets.
• Standardization does not guarantee a fixed range, whereas Min-Max scaling produces an
exact bounded range.
Example Programs
a) Standardization (Z-score)
1 import pandas as pd
2 import numpy as np
3 from sklearn . preprocessing import StandardScaler
4
5 # Sample data
6 df = pd . DataFrame ({
7 ’ age ’: [25 , 30 , 35 , 40 , 45] ,
8 ’ income ’: [50000 , 60000 , 75000 , 90000 , 120000]
9 })
10
11 scaler = StandardScaler ()
12 scaled = scaler . fit_transform ( df )
13 df_scaled = pd . DataFrame ( scaled , columns = df . columns )
14
15 print ( " Original mean :\ n " , df . mean () )
16 print ( " Original std :\ n " , df . std () )
17 print ( " \ nAfter scaling mean :\ n " , df_scaled . mean () )
18 print ( " After scaling std :\ n " , df_scaled . std () )
b) Min-Max Scaling
1 import pandas as pd
2 from sklearn . preprocessing import MinMaxScaler
3
4 df = pd . DataFrame ({
5 ’ age ’: [25 , 30 , 35 , 40 , 45] ,
6 ’ income ’: [50000 , 60000 , 75000 , 90000 , 120000]
7 })
8
9 scaler = MinMaxScaler ()
10 scaled = scaler . fit_transform ( df )
11 df_scaled = pd . DataFrame ( scaled , columns = df . columns )
12
13 print ( " Original min :\ n " , df . min () )
14 print ( " Original max :\ n " , df . max () )
15 print ( " \ nAfter scaling min :\ n " , df_scaled . min () )
16 print ( " After scaling max :\ n " , df_scaled . max () )
38
39
1 import pandas as pd
2 from sklearn . preprocessing import RobustScaler
3
4 # Include an outlier
5 df = pd . DataFrame ({
6 ’ value ’: [10 , 12 , 11 , 100 , 13 , 14 , 12] # 100 is outlier
7 })
8
9 scaler = RobustScaler ()
10 scaled = scaler . fit_transform ( df )
11 df [ ’ scaled ’] = scaled
12
13 print ( " Original :\ n " , df )
14 print ( " \ nMedian : " , df [ ’ value ’ ]. median () )
15 print ( " IQR : " , df [ ’ value ’ ]. quantile (0.75) - df [ ’ value ’ ]. quantile (0.25) )
16 print ( " Scaled median : " , df [ ’ scaled ’ ]. median () )
1 import pandas as pd
2 import numpy as np
3 from sklearn . model_selection import train_test_split
4 from sklearn . preprocessing import StandardScaler
5 from sklearn . svm import SVR
6 from sklearn . pipeline import make_pipeline
7 from sklearn . metrics import mean_squared_error
8
9 # Generate data with different scales
10 np . random . seed (42)
11 X = np . random . rand (100 , 2) * [100 , 0.1] # one feature large scale , one small
12 y = X [: , 0] * 2 + X [: , 1] * 100 + np . random . randn (100) * 5
13
14 X_train , X_test , y_train , y_test = train_test_split (X , y , test_size =0.2)
15
16 # SVM without scaling
17 svm_raw = SVR ()
18 svm_raw . fit ( X_train , y_train )
19 y_pred_raw = svm_raw . predict ( X_test )
20 mse_raw = mean_squared_error ( y_test , y_pred_raw )
21
22 # SVM with scaling
23 pipeline = make_pipeline ( StandardScaler () , SVR () )
24 pipeline . fit ( X_train , y_train )
25 y_pred_scaled = pipeline . predict ( X_test )
26 mse_scaled = mean_squared_error ( y_test , y_pred_scaled )
27
28 print ( f " MSE without scaling : { mse_raw :.4 f } " )
29 print ( f " MSE with scaling : { mse_scaled :.4 f } " )
1 import pandas as pd
2 import numpy as np
3 from sklearn . preprocessing import StandardScaler , MinMaxScaler , RobustScaler ,
MaxAbsScaler
4
5 # Create data with outliers
6 np . random . seed (42)
39
40
Example Programs
a) Standardization (Z-score)
1 import pandas as pd
2 import numpy as np
3 from sklearn . preprocessing import StandardScaler
4
5 # Sample data
6 df = pd . DataFrame ({
7 ’ age ’: [25 , 30 , 35 , 40 , 45] ,
8 ’ income ’: [50000 , 60000 , 75000 , 90000 , 120000]
9 })
10
11 scaler = StandardScaler ()
12 scaled = scaler . fit_transform ( df )
13 df_scaled = pd . DataFrame ( scaled , columns = df . columns )
14
15 print ( " Original mean :\ n " , df . mean () )
16 print ( " Original std :\ n " , df . std () )
17 print ( " \ nAfter scaling mean :\ n " , df_scaled . mean () )
18 print ( " After scaling std :\ n " , df_scaled . std () )
b) Min-Max Scaling
1 import pandas as pd
2 from sklearn . preprocessing import MinMaxScaler
3
4 df = pd . DataFrame ({
5 ’ age ’: [25 , 30 , 35 , 40 , 45] ,
6 ’ income ’: [50000 , 60000 , 75000 , 90000 , 120000]
7 })
8
9 scaler = MinMaxScaler ()
10 scaled = scaler . fit_transform ( df )
11 df_scaled = pd . DataFrame ( scaled , columns = df . columns )
12
13 print ( " Original min :\ n " , df . min () )
14 print ( " Original max :\ n " , df . max () )
15 print ( " \ nAfter scaling min :\ n " , df_scaled . min () )
16 print ( " After scaling max :\ n " , df_scaled . max () )
40
41
1 import pandas as pd
2 from sklearn . preprocessing import RobustScaler
3
4 # Include an outlier
5 df = pd . DataFrame ({
6 ’ value ’: [10 , 12 , 11 , 100 , 13 , 14 , 12] # 100 is outlier
7 })
8
9 scaler = RobustScaler ()
10 scaled = scaler . fit_transform ( df )
11 df [ ’ scaled ’] = scaled
12
13 print ( " Original :\ n " , df )
14 print ( " \ nMedian : " , df [ ’ value ’ ]. median () )
15 print ( " IQR : " , df [ ’ value ’ ]. quantile (0.75) - df [ ’ value ’ ]. quantile (0.25) )
16 print ( " Scaled median : " , df [ ’ scaled ’ ]. median () )
1 import pandas as pd
2 import numpy as np
3 from sklearn . model_selection import train_test_split
4 from sklearn . preprocessing import StandardScaler
5 from sklearn . svm import SVR
6 from sklearn . pipeline import make_pipeline
7 from sklearn . metrics import mean_squared_error
8
9 # Generate data with different scales
10 np . random . seed (42)
11 X = np . random . rand (100 , 2) * [100 , 0.1] # one feature large scale , one small
12 y = X [: , 0] * 2 + X [: , 1] * 100 + np . random . randn (100) * 5
13
14 X_train , X_test , y_train , y_test = train_test_split (X , y , test_size =0.2)
15
16 # SVM without scaling
17 svm_raw = SVR ()
18 svm_raw . fit ( X_train , y_train )
19 y_pred_raw = svm_raw . predict ( X_test )
20 mse_raw = mean_squared_error ( y_test , y_pred_raw )
21
22 # SVM with scaling
23 pipeline = make_pipeline ( StandardScaler () , SVR () )
24 pipeline . fit ( X_train , y_train )
25 y_pred_scaled = pipeline . predict ( X_test )
26 mse_scaled = mean_squared_error ( y_test , y_pred_scaled )
27
28 print ( f " MSE without scaling : { mse_raw :.4 f } " )
29 print ( f " MSE with scaling : { mse_scaled :.4 f } " )
1 import pandas as pd
2 import numpy as np
3 from sklearn . preprocessing import StandardScaler , MinMaxScaler , RobustScaler ,
MaxAbsScaler
4
5 # Create data with outliers
6 np . random . seed (42)
41
42
Feature Encoding
Theory
Feature encoding converts categorical data into numerical form suitable for machine learning
algorithms. Proper encoding ensures models can interpret categorical variables correctly.
Types of Categorical Encoding:
• Label Encoding: Assigns a unique integer to each category. Useful for ordinal data or
tree-based models.
• One-Hot Encoding: Creates binary columns for each category. Suitable for nominal
data and linear models.
• Ordinal Encoding: Maps categories to ordered integers. Useful for ordinal data with a
known order.
• Frequency Encoding: Replaces each category with its frequency. Effective for high
cardinality features and tree models.
• Target Encoding: Replaces each category with the mean of the target variable. Suitable
for classification/regression tasks with high cardinality features. Must use cross-validation
to avoid overfitting.
• Binary Encoding: Converts categories to binary digits and splits them into separate
columns. Memory-efficient for high-cardinality features.
Key Considerations:
• One-hot encoding can create many columns for features with high cardinality.
42
43
1 import pandas as pd
2 from sklearn . preprocessing import LabelEncoder
3
4 df = pd . DataFrame ({ ’ color ’: [ ’ red ’ , ’ blue ’ , ’ green ’ , ’ blue ’ , ’ red ’ ]})
5
6 # Using sklearn
7 le = LabelEncoder ()
8 df [ ’ color_encoded ’] = le . fit_transform ( df [ ’ color ’ ])
9
10 # Using pandas factorize
11 df [ ’ color_factor ’] = pd . factorize ( df [ ’ color ’ ]) [0]
12
13 print ( df )
14 print ( " Classes : " , le . classes_ )
15 # Output :
16 # color color_encoded color_factor
17 # 0 red 2 2
18 # 1 blue 0 0
19 # 2 green 1 1
20 # 3 blue 0 0
21 # 4 red 2 2
22 # Classes : [ ’ blue ’ ’ green ’ ’ red ’]
b) One-Hot Encoding
1 df = pd . DataFrame ({
2 ’ id ’: [1 ,2 ,3 ,4] ,
3 ’ color ’: [ ’ red ’ , ’ blue ’ , ’ green ’ , ’ blue ’] ,
4 ’ size ’: [ ’S ’ , ’M ’ , ’L ’ , ’M ’]
5 })
6
7 df_encoded = pd . get_dummies ( df , columns =[ ’ color ’ , ’ size ’ ])
8 df_dummy = pd . get_dummies ( df , columns =[ ’ color ’ , ’ size ’] , drop_first = True )
9
10 print ( df_encoded )
11 print ( df_dummy )
12 # Output :
13 # df_encoded :
14 # id color_blue color_green color_red size_L size_M size_S
15 # 0 1 0 0 1 0 0 1
16 # 1 2 1 0 0 0 1 0
17 # 2 3 0 1 0 1 0 0
18 # 3 4 1 0 0 0 1 0
19 # df_dummy :
20 # id color_green color_red color_blue size_M size_S
21 # 0 1 0 1 0 0 1
22 # 1 2 0 0 1 1 0
23 # 2 3 1 0 0 0 0
24 # 3 4 0 0 1 1 0
c) Ordinal Encoding
43
44
4 print ( df )
5 # Output :
6 # education education_ordinal
7 # 0 Bachelor 1
8 # 1 Master 2
9 # 2 PhD 3
10 # 3 Bachelor 1
11 # 4 Master 2
12 # 5 PhD 3
d) Frequency Encoding
1 df = pd . DataFrame ({
2 ’ category ’: [ ’A ’ , ’B ’ , ’A ’ , ’C ’ , ’B ’ , ’A ’ , ’C ’ , ’B ’ , ’A ’ , ’C ’] ,
3 ’ target ’: [1 ,0 ,1 ,0 ,1 ,0 ,1 ,0 ,1 ,1]
4 })
5
6 # Simple target encoding
7 target_map = df . groupby ( ’ category ’) [ ’ target ’ ]. mean ()
8 df [ ’ t a r g e t _e ncode d_si mple ’] = df [ ’ category ’ ]. map ( target_map )
9
10 # Cross - validated target encoding
11 from sklearn . model_selection import KFold
12 import numpy as np
13 df [ ’ targe t_encoded_cv ’] = np . nan
14 kf = KFold ( n_splits =3 , shuffle = True , random_state =42)
15 for train_idx , val_idx in kf . split ( df ) :
16 train , val = df . iloc [ train_idx ] , df . iloc [ val_idx ]
17 means = train . groupby ( ’ category ’) [ ’ target ’ ]. mean ()
18 df . loc [ val_idx , ’ target_encoded_cv ’] = val [ ’ category ’ ]. map ( means )
19 df [ ’ targe t_encoded_cv ’] = df [ ’ target_encoded_cv ’ ]. fillna ( df [ ’ target ’ ]. mean () )
20
21 print ( df )
22 # Output : df with columns category , target , target_encoded_simple ,
targ et_encoded_cv
44
45
1 import pandas as pd
2 import numpy as np
3 from sklearn . model_selection import train_test_split
4 from sklearn . preprocessing import StandardScaler , OneHotEncoder
5 from sklearn . compose import ColumnTransformer
6 from sklearn . pipeline import Pipeline
7 from sklearn . ensemble import Rand omFor estR egre ssor
8 from sklearn . metrics import mean_squared_error
9
10 # Create a mixed dataset
11 np . random . seed (42)
12 n_samples = 1000
13 df = pd . DataFrame ({
14 ’ age ’: np . random . randint (18 , 70 , n_samples ) ,
15 ’ income ’: np . random . exponential (50 , n_samples ) * 1000 ,
16 ’ education ’: np . random . choice ([ ’ High School ’ , ’ Bachelor ’ , ’ Master ’ , ’ PhD ’] ,
n_samples ) ,
17 ’ city ’: np . random . choice ([ ’ NYC ’ , ’ LA ’ , ’ Chicago ’ , ’ Houston ’ , ’ Phoenix ’] ,
n_samples ) ,
18 ’ purchases ’: np . random . poisson (5 , n_samples ) * 10
19 })
20
21 # Target : function of features + noise
22 df [ ’ spending ’] = ( df [ ’ age ’ ]*2 +
23 df [ ’ income ’ ]*0.01 +
45
46
• Reference counting: Each object tracks references; deallocated when count reaches zero.
46
47
47
48
– Use NumPy arrays instead of Python lists for large numeric collections.
– Arrays store data contiguously in memory, enabling vectorized operations.
– Using slots can reduce memory overhead by 30-50% because it prevents each
object from creating a dict .
48
49
49
50
50
51
1 import tracemalloc
2 import pandas as pd
3
4 tracemalloc . start ()
5
6 # Your code here
7 df = pd . read_csv ( ’ large_file . csv ’)
8 result = df . groupby ( ’ category ’) . sum ()
9
10 current , peak = tracemalloc . get_traced_memory ()
11 print ( f " Current memory : { current / 1024**2:.1 f } MB " )
51
52
1 import psutil
2 import os
3
4 def memory_usage () :
5 process = psutil . Process ( os . getpid () )
6 mem = process . memory_info () . rss / 1024**2 # MB
7 return mem
8
9 print ( f " Memory usage : { memory_usage () :.2 f } MB " )
1 import pandas as pd
2 import numpy as np
3 import psutil
4 import tracemalloc
5
6 def op ti mi zed_pipeline ( filepath ) :
7 """
8 Complete optimized data processing pipeline
9 """
10 tracemalloc . start ()
11 start_mem = psutil . Process () . memory_info () . rss / 1024**2
12
13 # 1. Read only necessary columns in chunks
14 chunk_size = 50000
15 chunks = []
16
17 for chunk in pd . read_csv ( filepath , chunksize = chunk_size ,
18 usecols =[ ’ id ’ , ’ category ’ , ’ value ’ , ’ timestamp ’ ]) :
19
20 # 2. Optimize data types per chunk
21 chunk [ ’ category ’] = chunk [ ’ category ’ ]. astype ( ’ category ’)
22
23 # 3. Downcast numeric columns
24 if chunk [ ’ value ’ ]. dtype == ’ float64 ’:
25 chunk [ ’ value ’] = pd . to_numeric ( chunk [ ’ value ’] , downcast = ’ float ’)
26
27 # 4. Process chunk ( vectorized operations )
28 chunk [ ’ value_squared ’] = chunk [ ’ value ’] ** 2
29 chunk [ ’ value_log ’] = np . log1p ( chunk [ ’ value ’ ])
30
31 # 5. Filter early
32 chunk = chunk [ chunk [ ’ value ’] > 0]
33
34 chunks . append ( chunk )
35
36 # 6. Clear any temporary variables
37 del chunk
38
39 # 7. Combine results
40 final_df = pd . concat ( chunks , ignore_index = True )
41
42 # 8. Final aggregations
43 result = final_df . groupby ( ’ category ’) . agg ({
52
53
• Pandas Optimizations
• NumPy Optimizations
• Parallel Processing
53
54
• Extract: Pull messy or scattered data from various sources (CSV files, databases, APIs,
JSON)
• Load: Save the polished result to a destination (new file, database, data warehouse)
• Maintainability: Update cleaning logic in one place rather than scattered scripts
• Idempotency: Running the same pipeline multiple times on the same input should pro-
duce identical output. This is crucial for reproducibility.
• Reporting: Track what changes were made during processing (duplicates removed, miss-
ing values filled, validation errors) to provide transparency.
54
55
• Excel spreadsheets
• APIs
Best practices:
• Potential outliers
Duplicate Removal
55
56
Standardization
Outlier Handling
• Required fields
56
57
57
58
46 return df , all_stats
47
48
49 # Usage
50 steps = [
51 ( ’ remove_duplicates ’ , remove_duplicates ) ,
52 ( ’ fill_missing ’ , fill_missing_values ) ,
53 ( ’ standardize ’ , standardize_text , [ ’ name ’ , ’ address ’ ])
54 ]
55
56 clean_data , stats = run_pipeline ( raw_data , steps )
3. Implementation Patterns
3.1 Function-Based Pipeline
The simplest approach chains together functions, each performing one cleaning operation:
1 def remov e_duplicates ( df ) :
2 initial_count = len ( df )
3 df = df . drop_duplicates ()
4 stats = { ’ duplicates_removed ’: initial_count - len ( df ) }
5 return df , stats
6
7 def f i l l_ m issing_values ( df ) :
8 stats = {}
9
10 # Handle numeric columns
11 num_cols = df . select_dtypes ( include =[ ’ number ’ ]) . columns
12 for col in num_cols :
13 missing = df [ col ]. isnull () . sum ()
14 if missing > 0:
15 df [ col ] = df [ col ]. fillna ( df [ col ]. median () )
16 stats [ f ’{ col } _filled ’] = missing
17
18 # Handle categorical columns
19 cat_cols = df . select_dtypes ( include =[ ’ object ’ , ’ category ’ ]) . columns
20 for col in cat_cols :
21 missing = df [ col ]. isnull () . sum ()
22 if missing > 0:
23 df [ col ] = df [ col ]. fillna ( ’ Unknown ’)
24 stats [ f ’{ col } _filled ’] = missing
25
26 return df , stats
27
28 def standardize_text ( df , columns ) :
29 for col in columns :
30 if col in df . columns :
31 df [ col ] = df [ col ]. str . strip () . str . lower ()
32 return df , { ’ text_standardized ’: len ( columns ) }
33
34 # Pipeline orchestrator
35 def run_pipeline ( df , steps ) :
36 " " " Execute a sequence of cleaning steps " " "
37 all_stats = {}
38
39 for step_name , step_func , * args in steps :
40 if args :
41 df , stats = step_func ( df , * args [0])
42 else :
43 df , stats = step_func ( df )
44 all_stats [ step_name ] = stats
45
58
59
46 return df , all_stats
47
48
49 # Usage
50 steps = [
51 ( ’ remove_duplicates ’ , remove_duplicates ) ,
52 ( ’ fill_missing ’ , fill_missing_values ) ,
53 ( ’ standardize ’ , standardize_text , [ ’ name ’ , ’ address ’ ])
54 ]
55
56 clean_data , stats = run_pipeline ( raw_data , steps )
59
60
48
49 def _ standardize_columns ( self , df ) :
50 text_cols = self . config . get ( ’ text_columns ’ , [])
51 for col in text_cols :
52 if col in df . columns :
53 df [ col ] = df [ col ]. str . strip () . str . lower ()
54 return df
55
56 def _handle_outliers ( self , df ) :
57 for col in self . config . get ( ’ numeric_columns ’ , []) :
58 if col not in df . columns :
59 continue
60
61 q1 = df [ col ]. quantile (0.25)
62 q3 = df [ col ]. quantile (0.75)
63 iqr = q3 - q1
64 lower_bound = q1 - 1.5 * iqr
65 upper_bound = q3 + 1.5 * iqr
66
67 outliers = (
68 ( df [ col ] < lower_bound ) | ( df [ col ] > upper_bound )
69 ) . sum ()
70
71 if outliers > 0 and self . config . get ( ’ cap_outliers ’ , True ) :
72 df [ col ] = df [ col ]. clip ( lower_bound , upper_bound )
73 self . stats [ ’ outliers_capped ’] += outliers
74
75 return df
76
77 def validate_data ( self , df , schema ) :
78 " " " Validate against a schema " " "
79 valid_rows = []
80
81 for idx , row in df . iterrows () :
82 try :
83 # Apply validation rules from schema
84 validated = self . _validate_row ( row , schema )
85 valid_rows . append ( validated )
86 except Exception as e :
87 self . errors . append ({ ’ row ’: idx , ’ error ’: str ( e ) })
88 self . stats [ ’ validation_errors ’] += 1
89
90 return pd . DataFrame ( valid_rows )
91
92 def run ( self , df ) :
93 " " " Execute complete pipeline " " "
94 cleaned = self . clean_data ( df )
95
96 if hasattr ( self , ’ validation_schema ’) :
97 cleaned = self . validate_data ( cleaned , self . validation_schema )
98
99 return {
100 ’ data ’: cleaned ,
101 ’ stats ’: self . stats ,
102 ’ errors ’: self . errors
103 }
104
105
106 # Usage
107 config = {
108 ’ numeric_columns ’: [ ’ age ’ , ’ salary ’ , ’ experience ’] ,
109 ’ c a t egorical_columns ’: [ ’ department ’ , ’ gender ’] ,
110 ’ text_columns ’: [ ’ name ’ , ’ address ’] ,
60
61
61
62
1 import yaml
2 import pandas as pd
3
4 class C o n figurablePipeline :
5 def __init__ ( self , config_path ) :
6 with open ( config_path , ’r ’) as f :
7 self . config = yaml . safe_load ( f )
8 self . stats = {}
9
10 def run ( self ) :
11 # Extract
12 df = self . _extract ()
13
14 # Transform
15 df = self . _clean_data ( df )
16
17 # Validate
18 df , errors = self . _validate ( df )
19
20 # Load
21 self . _load ( df )
22
23 return {
24 ’ data ’: df ,
25 ’ stats ’: self . stats ,
26 ’ errors ’: errors
27 }
28
29 def _extract ( self ) :
30 sources = self . config [ ’ data_sources ’]
31 # Implementation for reading from various sources
32 # ...
33
34 def _clean_data ( self , df ) :
35 # Apply cleaning steps from config
36 # ...
37 return df
62
63
63
64
64
65
20 # Logging setup
21 self . logger = logging . getLogger ( __name__ )
22 if log_path :
23 handler = logging . FileHandler ( log_path )
24 formatter = logging . Formatter (
25 ’ %( asctime ) s - %( name ) s - %( levelname ) s - %( message ) s ’
26 )
27 handler . setFormatter ( formatter )
28 self . logger . addHandler ( handler )
29 self . logger . setLevel ( logging . INFO )
30
31 self . stats = {
32 ’ start_time ’: datetime . now () ,
33 ’ end_time ’: None ,
34 ’ input_rows ’: 0 ,
35 ’ output_rows ’: 0 ,
36 ’ steps ’: [] ,
37 ’ errors ’: []
38 }
39
40 def extract ( self ) -> pd . DataFrame :
41 " " " Extract data from configured source " " "
42 source = self . config [ ’ data_source ’]
43
44 if source [ ’ format ’] == ’ csv ’:
45 df = pd . read_csv ( source [ ’ path ’ ])
46 elif source [ ’ format ’] == ’ excel ’:
47 df = pd . read_excel ( source [ ’ path ’ ])
48 elif source [ ’ format ’] == ’ json ’:
49 df = pd . read_json ( source [ ’ path ’ ])
50 else :
51 raise ValueError ( " Unsupported format " )
52
53 self . stats [ ’ input_rows ’] = len ( df )
54 return df
55
56 def clean ( self , df : pd . DataFrame ) -> pd . DataFrame :
57 " " " Apply cleaning operations " " "
58
59 # Remove duplicates
60 before = len ( df )
61 df = df . drop_duplicates ()
62 removed = before - len ( df )
63
64 self . stats [ ’ steps ’ ]. append ({
65 ’ step ’: ’ remove_duplicates ’ ,
66 ’ rows_removed ’: removed
67 })
68
69 # Handle missing values
70 missing_filled = 0
71 for col in df . columns :
72 missing = df [ col ]. isnull () . sum ()
73
74 if missing == 0:
75 continue
76
77 if pd . api . types . is_numeric_dtype ( df [ col ]) :
78 df [ col ] = df [ col ]. fillna ( df [ col ]. median () )
79 else :
80 df [ col ] = df [ col ]. fillna ( " Unknown " )
81
82 missing_filled += missing
65
66
83
84 self . stats [ ’ steps ’ ]. append ({
85 ’ step ’: ’ handle_missing ’ ,
86 ’ values_filled ’: missing_filled
87 })
88
89 return df
90
91 def validate ( self , df : pd . DataFrame ) -> Tuple [ pd . DataFrame , List [ Dict ]]:
92 " " " Validate dataset using schema rules " " "
93
94 schema = self . config . get ( ’ validation ’ , {}) . get ( ’ schema ’ , {})
95 valid_rows = []
96 errors = []
97
98 for idx , row in df . iterrows () :
99 row_errors = []
100
101 for field , rules in schema . items () :
102 value = row . get ( field )
103
104 if rules . get ( ’ required ’) and pd . isna ( value ) :
105 row_errors . append ( f " { field } missing " )
106
107 if ’ min ’ in rules and value < rules [ ’ min ’ ]:
108 row_errors . append ( f " { field } below minimum " )
109
110 if ’ max ’ in rules and value > rules [ ’ max ’ ]:
111 row_errors . append ( f " { field } above maximum " )
112
113 if row_errors :
114 errors . append ({
115 " row_index " : idx ,
116 " errors " : row_errors
117 })
118 else :
119 valid_rows . append ( row )
120
121 return pd . DataFrame ( valid_rows ) , errors
122
123 def transform ( self , df : pd . DataFrame ) -> pd . DataFrame :
124 " " " Apply feature transformations " " "
125
126 transforms = self . config . get ( " transformations " , {})
127
128 for encode in transforms . get ( " encoding " , []) :
129 col = encode [ " column " ]
130
131 if encode [ " method " ] == " one_hot " :
132 dummies = pd . get_dummies ( df [ col ] , prefix = col )
133 df = pd . concat ([ df , dummies ] , axis =1)
134 df . drop ( columns =[ col ] , inplace = True )
135
136 return df
137
138 def load ( self , df : pd . DataFrame , errors : List [ Dict ]) :
139 " " " Save cleaned data and reports " " "
140
141 output = self . config [ " output " ]
142
143 df . to_csv ( output [ " clean_path " ] , index = False )
144
145 if errors :
66
67
67
68
• Idempotency: Running the pipeline multiple times with the same input should produce
the same output every time.
• Error Handling: Pipelines should fail gracefully and capture errors for debugging.
Pipeline Components
• Extract: Read data from sources such as CSV, Excel, JSON, or databases.
• Clean: Remove duplicates, handle missing values, standardize text fields, and manage
outliers.
• Validate: Check the dataset against schema rules including required fields, data types,
ranges, and patterns.
• Load: Save the cleaned dataset along with logs, statistics, and error reports.
Implementation Patterns
• Function-based Pipeline: Uses a simple sequence of functions for data cleaning.
Testing Strategies
• Unit Testing: Test individual cleaning functions separately.
• Integration Testing: Test the complete pipeline using small sample datasets.
Best Practices
• Always work on copies of data to avoid modifying the original dataset.
• Handle edge cases such as empty DataFrames or columns with all missing values.
• Plan for schema evolution as datasets and requirements change over time.
68
69
1. Data Ingestion
Data ingestion is the process of collecting and importing data from various sources into a system
for further processing. The sources may include databases, CSV files, APIs, web applications,
sensors, or streaming platforms.
Data ingestion can be classified into two main types:
• Batch Ingestion: Data is collected and processed in large batches at scheduled intervals.
• Real-Time (Streaming) Ingestion: Data is continuously ingested as it is generated.
The goal of data ingestion is to ensure that raw data from different sources is reliably
transferred into the pipeline.
2. Data Transformation
Data transformation involves cleaning, organizing, and converting raw data into a format that
is suitable for analysis or machine learning tasks. This stage is crucial because raw data often
contains missing values, inconsistencies, or errors.
Common transformation operations include:
The objective of this step is to produce high-quality and structured data that can be easily
used for analysis and modeling.
3. Data Storage
Data storage refers to storing the processed data in systems where it can be accessed for analysis,
reporting, or machine learning. Proper storage ensures efficient retrieval, scalability, and security
of data.
Common storage systems include:
After storage, the data can be accessed by analytics tools, dashboards, or machine learning
pipelines.
69
70
Summary
A well-designed data pipeline ensures smooth data flow from ingestion to transformation and
finally to storage. By automating these stages, organizations can efficiently process large volumes
of data and support reliable data-driven decision making.
70
Chapter-4
Applied Statistics and Exploratory Analysis
• Positive Covariance: When a larger value of one variable corresponds to a larger value of the other variable.
• Negative Covariance: When a larger value of one variable corresponds to a smaller value of the other variable.
• 0: No linear relationship
Formula (Pearson Correlation): The most common type, measuring the strength of a linear relationship.
Cov(X, Y )
rXY = (2)
sX sY
where sX and sY are the sample standard deviations of X and Y .
The cov2corr function is a standard utility to convert a covariance matrix into a correlation matrix by dividing by the outer
product of the standard deviations.
4.1.2 Skewness
Skewness is a measure of the asymmetry of the probability distribution of a real-valued random variable about its mean.
• Symmetrical Distribution: The distribution is bell-shaped and balanced (e.g., Normal distribution). Skewness is
approximately zero.
Positive Skew (Right-skewed): The distribution has a long tail on the right. The mean is typically greater than the
median.
The formula for sample skewness is often based on the third central moment:
1
Pn 3
n i=1 (Xi − X̄)
Skewness = (3)
s3
Negative Skew (Left-skewed): The distribution has a long tail on the left. The mean is typically less than the median.
4.1.3 Kurtosis
Kurtosis is a measure of the “tailedness” of the probability distribution. It describes the combined weight of the tails relative
to the center of the distribution.
• High Kurtosis (Leptokurtic): The distribution has heavy tails and a sharp, narrow peak. This indicates more outliers.
• Low Kurtosis (Platykurtic): The distribution has light tails and a flat, spread-out peak. This indicates fewer or no
outliers.
Formula: Kurtosis is based on the fourth central moment. It is often reported as excess kurtosis, which is the kurtosis value
minus 3 (the kurtosis of a normal distribution).
1
Pn 4
n i=1 (Xi − X̄)
Kurtosis = (4)
s4
1
2
12 # Using NumPy
13 print ( " Using NumPy : " )
14 cov_numpy = np . cov ( height , weight , ddof =1) [0 , 1]
15 corr_numpy = np . corrcoef ( height , weight ) [0 , 1]
16
17 print ( f " Covariance : { cov_numpy :.2 f } " )
18 print ( f " Correlation ( Pearson r ) : { corr_numpy :.4 f }\ n " )
19
20 # Using Pandas
21 print ( " Using Pandas : " )
22 df = pd . DataFrame ({ ’ Height ’: height , ’ Weight ’: weight })
23
30 # Interpretation
31 if abs ( corr_pandas ) > 0.7:
32 print ( " \ nInterpretation : Strong linear relationship . " )
33 else :
34 print ( " \ nInterpretation : Moderate or weak linear relationship . " )
Listing 1: Calculating Covariance and Correlation in Python
Explanation
• [Link]() and [Link]() compute the covariance and correlation matrices. The element [0,1] represents the
relationship between the two variables.
• Pandas provides convenient methods .cov() and .corr() to compute these statistics directly from DataFrame columns.
The positive value confirms that height and weight increase together, and the correlation value close to 1 indicates a strong
positive linear relationship.
2
3
33 df = pd . DataFrame ({
34 ’ Normal ’: normal_data ,
35 ’ Pos_Skew ’: positive_skew ,
36 ’ Neg_Skew ’: negative_skew
37 })
38
39 print ( df . describe () )
40
41 print ( " \n - - - Pandas Skew and Kurtosis ---" )
42 stats_df = df . agg ([ ’ skew ’ , ’ kurtosis ’ ]) . round (4)
43 print ( stats_df )
Listing 2: Calculating Skewness and Kurtosis in Python
Explanation
• When fisher=True, the function returns excess kurtosis where a normal distribution has a value of 0.
• The Pandas method .agg() allows multiple statistical functions such as skew and kurtosis to be applied simultaneously
to DataFrame columns.
• X = {2, 4, 6, 8, 10}
• Number of observations: n = 5
• X3 − X̄ = 6 − 6 = 0, Y3 − Ȳ = 8 − 8 = 0, product = 0
• X4 − X̄ = 8 − 6 = 2, Y4 − Ȳ = 10 − 8 = 2, product = 4
• X5 − X̄ = 10 − 6 = 4, Y5 − Ȳ = 12 − 8 = 4, product = 16
X
(X − X̄)(Y − Ȳ ) = 16 + 4 + 0 + 4 + 16 = 40
Step 3: Covariance
P
(X − X̄)(Y − Ȳ )
Cov(X, Y ) =
n−1
40
Cov(X, Y ) = = 10
4
Step 4: Standard Deviations
For X
(−4)2 + (−2)2 + 02 + 22 + 42
= 16 + 4 + 0 + 4 + 16 = 40
40 √
r
sX = = 10 = 3.162
4
For Y
(−4)2 + (−2)2 + 02 + 22 + 42 = 40
40 √
r
sY = = 10 = 3.162
4
Step 5: Correlation
Cov(X, Y )
r=
sX sY
3
4
10
r=
(3.162)(3.162)
10
r= =1
10
Result
• Correlation = 1
• Interpretation: Perfect positive linear relationship
X = {2, 4, 6, 8, 10}
Step 1: Mean
X̄ = 6
Step 2: Deviations
• 2 − 6 = −4, (−4)3 = −64
• 4 − 6 = −2, (−2)3 = −8
• 6 − 6 = 0, 03 = 0
• 8 − 6 = 2, 23 = 8
• 10 − 6 = 4, 43 = 64
X
(X − X̄)3 = −64 − 8 + 0 + 8 + 64 = 0
Step 3: Standard Deviation
r
40
s= = 3.162
4
Step 4: Skewness
1
(X − X̄)3
P
n
Skewness =
s3
0/5
Skewness = =0
(3.162)3
Result
• Skewness = 0
• Interpretation: Distribution is symmetrical
X
(X − X̄)4 = 256 + 16 + 0 + 16 + 256 = 544
Step 2: Kurtosis
1
(X − X̄)4
P
n
Kurtosis =
s4
s = 3.162
s4 = 100
544/5
Kurtosis =
100
108.8
Kurtosis = = 1.088
100
Step 3: Excess Kurtosis
= 1.088 − 3 = −1.912
Result
4
5
• Kurtosis = 1.088
• Random Variable: A variable whose possible values are numerical outcomes of a random phenomenon.
• Probability Density Function (PDF): For continuous variables (e.g., Normal distribution), the PDF describes the
relative likelihood that the variable takes on a given value. The area under the PDF curve over an interval represents the
probability of the variable falling in that interval.
• Cumulative Distribution Function (CDF): The CDF gives the probability that a random variable takes a value less
than or equal to a specific point.
F (x) = P (X ≤ x)
• Common Distributions: The [Link] module provides a wide range of distributions, including:
Each distribution provides methods for calculating PDF, CDF, and generating random samples.
• Random Sample:
A sample drawn such that every member of the population has an equal and independent chance of being selected. This
is essential for making valid statistical inferences.
• Sampling Distribution:
The probability distribution of a given statistic (such as the sample mean) obtained from many random samples drawn
from the same population.
The standard deviation of this distribution is called the standard error, which measures the precision of the sample statistic
as an estimate of the population parameter.
5
6
α = 0.05
5. Compute the p-value
The p-value is the probability of observing a test statistic as extreme as or more extreme than the observed value, assuming
the null hypothesis is true.
6. Critical Region
The critical region is the set of values of the test statistic that leads to rejection of the null hypothesis. The boundaries
of this region are known as critical values, determined by α and the sampling distribution.
6
7
Explanation
Explanation
• Simulation-based critical region: Thousands of experiments are simulated under the null hypothesis to construct the
sampling distribution.
• Critical values: The boundaries of the rejection region correspond to the percentiles defined by the significance level α.
• Effect of sample size: Increasing sample size reduces the width of the critical region, showing that larger samples
produce more precise estimates.
• [Link]() performs an exact binomial hypothesis test and returns the corresponding p-value for the observed
data.
7
8
52 df_anova = pd . DataFrame ({
53 ’ value ’: np . concatenate ([ group1 , group2 , group3 ]) ,
54 ’ group ’: [ ’A ’ ]*30 + [ ’B ’ ]*30 + [ ’C ’ ]*30
55 })
56
57 model = ols ( ’ value ~ group ’ , data = df_anova ) . fit ()
58 anova_table = sm . stats . anova_lm ( model , typ =2)
59
60 print ( anova_table )
Listing 5: Common Statistical Tests in Python
Explanation
• [Link] ind() performs an independent samples t-test to compare the means of two independent groups.
• [Link]() is a non-parametric alternative to the t-test used when the data does not meet normality assump-
tions.
• [Link]() performs a chi-square goodness-of-fit test to compare observed frequencies with expected frequencies.
• [Link]() calculates the Pearson correlation coefficient and its p-value, measuring linear relationships between
variables.
• [Link]() computes the Spearman rank correlation coefficient, which measures monotonic relationships and is
more robust to outliers.
• statsmodels provides advanced statistical modeling tools. The OLS function fits a linear model, and anova lm() generates
an ANOVA table for testing differences among multiple groups.
8
9
• Independent Variables (X): Predictor variables used to explain variation in the dependent variable.
• Regression Coefficients (β): Parameters representing the change in Y for a one-unit change in X.
Yi = β0 + β1 Xi + ϵi
where
• β0 is the intercept
• R-squared (R2 )
SSres
R2 = 1 −
SStot
Measures the proportion of variance in the dependent variable explained by the regression model. Values range from 0 to
1.
• Adjusted R-squared
2 n−1
Radj = 1 − (1 − R2 )
n−k−1
Adjusted R2 penalizes the model for including too many predictors and provides a more reliable measure when multiple
variables are used.
• F-statistic
M Sreg
F =
M Sres
Tests whether the regression model explains a significant amount of variance in the dependent variable.
• t-statistic
β̂j
t=
SE(β̂j )
Used to test whether an individual regression coefficient is significantly different from zero.
9
10
Model Assumptions
For valid statistical inference, Ordinary Least Squares (OLS) regression relies on several key assumptions:
• Linearity: The relationship between the independent variable(s) X and the dependent variable Y is linear.
• Homoscedasticity: The variance of the residuals remains constant across all levels of the independent variables.
• Normality: Residuals are normally distributed. This assumption is particularly important for statistical inference such
as hypothesis testing and confidence intervals.
Trend Analysis
Trend analysis examines patterns and long-term movements in data over time. It is commonly used in economics, finance, and
time series analysis to identify systematic changes in a variable.
Common Types of Trend Models
• Linear Trend
Yt = β0 + β1 t + εt
This model assumes a constant absolute change in the dependent variable for each unit increase in time.
• Quadratic Trend
Yt = β0 + β1 t + β2 t2 + εt
This model allows for acceleration or deceleration in the trend over time.
• Seasonal (St ): Represents regular patterns that repeat over fixed time intervals.
• Residual (Rt ): Represents the irregular or random component remaining after removing trend and seasonal effects.
Yt = Tt + St + Rt
where:
10
11
53 # Residual plot
54 axes [1]. scatter ( df [ ’ predicted ’] , df [ ’ residuals ’] , alpha =0.6)
55 axes [1]. axhline ( y =0 , color = ’r ’ , linestyle = ’ -- ’ , linewidth =1)
56
57 axes [1]. set_xlabel ( ’ Predicted Values ’)
58 axes [1]. set_ylabel ( ’ Residuals ’)
59 axes [1]. set_title ( ’ Residual Plot ’)
60 axes [1]. grid ( True , alpha =0.3)
61
62 plt . tight_layout ()
63 plt . show ()
64
65 # Model diagnostics
66 print ( " \ n === Model Diagnostics === " )
67 print ( f "R - squared : { results_formula . rsquared :.4 f } " )
68 print ( f " Adjusted R - squared : { results_formula . rsquared_adj :.4 f } " )
69 print ( f "F - statistic : { results_formula . fvalue :.2 f } " )
70 print ( f "F - statistic p - value : { results_formula . f_pvalue :.4 e } " )
71 print ( f " Mean of residuals : { results_formula . resid . mean () :.4 e } " )
Listing 7: Simple Linear Regression using Statsmodels
• The slope coefficient (approximately 3500) indicates that each additional year of education is associated with roughly
$3500 higher income.
• The intercept represents the estimated income when education is zero (mainly used for model positioning).
• The R2 value around 0.50–0.60 suggests that education explains approximately 50–60% of the variation in income.
• The residual plot helps assess assumptions such as homoscedasticity and independence.
• The mean of residuals should be close to zero, confirming that the model provides unbiased estimates.
11
12
11 # Residual diagnostics
12 residuals = results_multi . resid
13 fitted = results_multi . fittedvalues
14
15 fig , axes = plt . subplots (1 , 2 , figsize =(14 , 5) )
16
17 # Residuals vs fitted
18 axes [0]. scatter ( fitted , residuals , alpha =0.6)
19 axes [0]. axhline ( y =0 , color = ’r ’ , linestyle = ’ -- ’)
20 axes [0]. set_xlabel ( ’ Fitted Values ’)
21 axes [0]. set_ylabel ( ’ Residuals ’)
22 axes [0]. set_title ( ’ Residuals vs Fitted ’)
23 axes [0]. grid ( True , alpha =0.3)
24
25 # Q - Q plot for normality
26 sm . qqplot ( residuals , line = ’s ’ , ax = axes [1])
27 axes [1]. set_title ( ’Q - Q Plot ( Normality Check ) ’)
28
29 plt . tight_layout ()
30 plt . show ()
Listing 10: Quadratic Effect Interpretation and Residual Diagnostics
Interpretation:
• The positive coefficient for age indicates income initially increases with age.
12
13
• The negative coefficient for age squared shows diminishing returns, leading to a peak in income at a certain age.
• Residual plots and Q-Q plots are used to check model assumptions: homoscedasticity and normality of residuals.
12 df_interact = pd . DataFrame ({
13 ’ education ’: education ,
14 ’ experience ’: experience ,
15 ’ income ’: true_income
16 })
17
• The positive coefficient for the interaction term (education:experience) indicates that the effect of experience on income
grows as education increases.
• For example, at 10 years of education, one additional year of experience increases income by a smaller amount compared
to 16 years of education.
• The plot shows how income curves steepen for higher education levels, demonstrating that education amplifies the return
to experience.
13
14
• Trend Component: Captures the long-term upward progression of atmospheric CO2. In this dataset, CO2 increased
from approximately 315 ppm (1958) to 370 ppm (2001), averaging ∼ 1.1 ppm/year.
• Residual Component: Should contain no systematic patterns; it represents random variation after removing trend and
seasonality. The residual plot confirms minimal autocorrelation.
• Variance explained: The trend and seasonal components account for the majority of variation in the CO2 series, while
residuals are small.
14
15
14 ARIMA ,
15 model_kwargs = dict ( order =(1 ,1 ,1) , trend = ’t ’)
16 )
17 stlf_res = stlf . fit ()
18
19 forecast_steps = 24
20 forecast = stlf_res . forecast ( forecast_steps )
21
22 # Visualization
23 plt . figure ( figsize =(14 ,8) )
24 plt . plot ( elec_equip . index [ -60:] , elec_equip [ ’ production ’ ][ -60:] , ’b - ’ , label = ’ Historical ’ , linewidth =2)
25 plt . plot ( forecast . index , forecast , ’r - ’ , label = ’ Forecast ’ , linewidth =2)
26 plt . xlabel ( ’ Date ’) ; plt . ylabel ( ’ Production ’)
27 plt . title ( ’ STL + ARIMA Forecast of Electrical Equipment Production ’)
28 plt . legend () ; plt . grid ( True , alpha =0.3)
29 plt . show ()
Listing 15: STL + ARIMA Forecasting
Key Points:
• STL decomposes the series into trend, seasonal, and residual components.
• The final forecast is the sum of ARIMA predictions and seasonal component.
Interpretation:
• MSTL separates the time series into trend, multiple seasonalities (daily, weekly), and residuals.
• Variance decomposition quantifies how much of the total variation is explained by each component.
• Useful for high-frequency data (hourly, minute-level) with nested seasonal patterns.
“Exploratory data analysis is detective work—numerical detective work—or graphical detective work.”
15
16
Inferential Methods
Inferential methods use sample data to make generalizations about populations. In EDA, inferential thinking helps:
• Assess whether observed patterns are likely to be real.
• Quantify uncertainty in exploratory findings.
• Guide hypothesis generation for confirmatory analysis.
Key Concepts: Sampling, uncertainty, and generalizability.
16
17
Sampling Strategies:
Goal: Make inferences from a sample about a larger population, estimating both value and uncertainty.
• Missing value detection: identify columns with null values and calculate percentages
17
18
Automated visualizations:
2. Data Cleaning: Handle missing values, remove duplicates, standardize data types.
3. Univariate Analysis: Generate summary statistics; create appropriate visualizations; flag potential issues.
4. Bivariate Analysis: Analyze relationships; generate correlation matrices, scatter plots, cross-tabs.
5. Report Generation: Compile findings into a structured report with key visualizations and patterns.
18
19
8. Summary
Automation of EDA workflows using Python leverages libraries such as pandas, NumPy, Matplotlib, and Seaborn to system-
atically explore datasets. This approach embodies the “detective work” philosophy of EDA while making the process efficient,
reproducible, and scalable for real-world data analysis tasks. listings xcolor
1 import pandas as pd
2 import numpy as np
3 import matplotlib . pyplot as plt
4 import seaborn as sns
5 from scipy import stats
6 import warnings
7 warnings . fil terwarni ngs ( ’ ignore ’)
8
9 plt . style . use ( ’ seaborn - v0_8 - darkgrid ’)
10 sns . set_palette ( " husl " )
11
12 class AutomatedEDA :
13 def __init__ ( self , df , target = None ) :
14 self . df = df . copy ()
15 self . target = target
16 self . numeric_cols = df . select_dtypes ( include =[ np . number ]) . columns . tolist ()
17 self . c a t e g o r i c a l _ c o l s = df . select_dtypes ( include =[ ’ object ’ , ’ category ’ ]) . columns . tolist ()
18 self . datetime_cols = df . select_dtypes ( include =[ ’ datetime64 ’ ]) . columns . tolist ()
19
20 def r u n_ c o m p l e t e _ e d a ( self ) :
21 print ( " = " *70)
22 print ( " AUTOMATED EXPLORATORY DATA ANALYSIS REPORT " )
23 print ( " = " *70)
24 self . data_overview ()
25 self . d a t a _ q u a l i t y _ c h e c k ()
26 self . u n i v a r i a t e _ a n a l y s i s ()
27 self . b i v a r i a t e _ a n a l y s i s ()
28 self . g e n e r a t e _ s u m m a r y ()
29
30 def data_overview ( self ) :
31 print ( " \ n1 . DATASET OVERVIEW " )
32 print ( " -" *40)
33 print ( f " Dataset Shape : { self . df . shape [0]} rows { self . df . shape [1]} columns " )
34 print ( f " \ nFirst 5 rows : " )
35 print ( self . df . head () )
36 print ( f " \ nLast 5 rows : " )
37 print ( self . df . tail () )
38 print ( f " \ nColumn Names : " )
39 for col in self . df . columns :
40 print ( f " { col } " )
41
42 def d a t a _ q u a l i t y _ c h e c k ( self ) :
43 print ( " \ n2 . DATA QUALITY ASSESSMENT " )
44 print ( " -" *40)
45 print ( " \ n2 .1 Data Types : " )
46 dtype_counts = self . df . dtypes . value_counts ()
47 for dtype , count in dtype_counts . items () :
48 print ( f " { dtype }: { count } columns " )
49
50 print ( " \ n2 .2 Missing Values : " )
51 missing = self . df . isnull () . sum ()
52 missing_pct = ( missing / len ( self . df ) ) * 100
53 missing_df = pd . DataFrame ({ ’ Missing_Count ’: missing , ’ Mi ss in g _P er ce n t ’: missing_pct }) . sort_values ( ’ Mi ss in g _P er ce n t ’ ,
ascending = False )
54 m i ss i n g _ w i t h _ d a t a = missing_df [ missing_df [ ’ Missing_Count ’] > 0]
55 if len ( m i s s i n g _ w i t h _ d a t a ) > 0:
56 print ( m i s s i n g _ w i t h _ d a t a )
57 print ( f " \ nTotal Missing Values : { self . df . isnull () . sum () . sum () } " )
58 else :
59 print ( " No missing values found " )
60
61 print ( " \ n2 .3 Duplicate Rows : " )
62 duplicates = self . df . duplicated () . sum ()
63 if duplicates > 0:
64 print ( f " Found { duplicates } duplicate rows ({ duplicates / len ( self . df ) *100:.2 f }%) " )
65 else :
66 print ( " No duplicate rows found " )
67
68 print ( " \ n2 .4 Memory Usage : " )
69 memory_usage = self . df . memory_usage ( deep = True ) . sum () / 1024**2
70 print ( f " Total memory : { memory_usage :.2 f } MB " )
71
72 def u n i v a r i a t e _ a n a l y s i s ( self ) :
73 print ( " \ n3 . UNIVARIATE ANALYSIS " )
74 print ( " -" *40)
75 if self . numeric_cols :
76 print ( " \ n3 .1 Numeric Variables Summary : " )
77 print ( self . df [ self . numeric_cols ]. describe () . round (3) )
78 print ( " \ nShape Statistics : " )
79 for col in self . numeric_cols [:5]:
80 data = self . df [ col ]. dropna ()
81 skewness = stats . skew ( data )
82 kurt = stats . kurtosis ( data )
83 print ( f " \ n { col }: " )
84 print ( f " Skewness : { skewness :.3 f } " )
85 print ( f " Kurtosis : { kurt :.3 f } " )
86 print ( f " Range : { data . max () - data . min () :.3 f } " )
87 print ( f " IQR : { data . quantile (0.75) - data . quantile (0.25) :.3 f } " )
88
89 if self . c a t e g o r i c a l _ c o l s :
90 print ( " \ n3 .2 Categorical Variables Summary : " )
91 for col in self . ca t e g o r i c a l _ c o l s [:5]:
92 print ( f " \ n { col }: " )
93 print ( f " Unique values : { self . df [ col ]. nunique () } " )
94 print ( f " Top value : { self . df [ col ]. mode () [0]} " )
95 print ( f " Top frequency : { self . df [ col ]. value_counts () . iloc [0]} " )
96
97 self . pl o t_ un iv a ri at e ()
98
99 def pl ot_ un iv a ri at e ( self ) :
100 n_numeric = len ( self . numeric_cols )
101 n_categorical = len ( self . c a t e g o r i c a l _ c o l s )
102
103 if n_numeric > 0:
104 n_cols = min (3 , n_numeric )
105 n_rows = ( min ( n_numeric , 6) + n_cols - 1) // n_cols
106 fig , axes = plt . subplots ( n_rows , n_cols , figsize =(5* n_cols , 4* n_rows ) )
107 axes = axes . flatten () if n_rows > 1 else [ axes ] if n_cols == 1 else axes
108 for idx , col in enumerate ( self . numeric_cols [:6]) :
109 if idx < len ( axes ) :
110 self . df [ col ]. hist ( ax = axes [ idx ] , bins =30 , alpha =0.7 , color = ’ steelblue ’ , edgecolor = ’ black ’)
111 axes [ idx ]. axvline ( self . df [ col ]. mean () , color = ’ red ’ , linestyle = ’ -- ’ , label = ’ Mean ’)
112 axes [ idx ]. axvline ( self . df [ col ]. median () , color = ’ green ’ , linestyle = ’ - ’ , label = ’ Median ’)
113 axes [ idx ]. set_title ( f ’ Distribution of { col } ’)
19
20
20
21
233 ’ age ’: np . random . normal (30 ,15 , n ) . clip (0 ,80) . round (1) ,
234 ’ sibsp ’: np . random . choice ([0 ,1 ,2 ,3 ,4 ,5] , n , p =[0.6 ,0.2 ,0.1 ,0.05 ,0.03 ,0.02]) ,
235 ’ parch ’: np . random . choice ([0 ,1 ,2 ,3 ,4 ,5] , n , p =[0.7 ,0.15 ,0.08 ,0.04 ,0.02 ,0.01]) ,
236 ’ fare ’: np . random . gamma (5 ,10 , n ) . round (2) ,
237 ’ embarked ’: np . random . choice ([ ’C ’ , ’Q ’ , ’S ’] , n , p =[0.3 ,0.2 ,0.5])
238 })
239 df_titanic . loc [ np . random . choice (n ,50) , ’ age ’] = np . nan
240 df_titanic . loc [ np . random . choice (n ,20) , ’ embarked ’] = np . nan
241 eda2 = AutomatedEDA ( df_titanic , target = ’ survived ’)
242 eda2 . r u n _ c o m p l e t e _ e d a ()
243
244 # --- Example 3: Custom Dataset ---
245 np . random . seed (42)
246 n = 1000
247 df_custom = pd . DataFrame ({
248 ’ customer_id ’: range (1000 ,1000+ n ) ,
249 ’ age ’: np . random . normal (40 ,15 , n ) . clip (18 ,90) . astype ( int ) ,
250 ’ income ’: np . random . gamma (5 ,10000 , n ) . round (0) ,
251 ’ credit_score ’: np . random . normal (700 ,50 , n ) . clip (300 ,850) . astype ( int ) ,
252 ’ p ur ch as e _a mo un t ’: np . random . exponential (100 , n ) . round (2) ,
253 ’ frequency ’: np . random . poisson (5 , n ) ,
254 ’ satisfaction ’: np . random . choice ([1 ,2 ,3 ,4 ,5] , n , p =[0.1 ,0.15 ,0.25 ,0.3 ,0.2]) ,
255 ’ region ’: np . random . choice ([ ’ North ’ , ’ South ’ , ’ East ’ , ’ West ’] , n ) ,
256 ’ member_type ’: np . random . choice ([ ’ Bronze ’ , ’ Silver ’ , ’ Gold ’ , ’ Platinum ’] , n , p =[0.4 ,0.3 ,0.2 ,0.1]) ,
257 ’ last_purchase ’: pd . date_range ( ’ 2023 -01 -01 ’ , periods =n , freq = ’D ’)
258 })
259 eda3 = AutomatedEDA ( df_custom , target = ’ pu r ch as e _a mo un t ’)
260 eda3 . r u n _ c o m p l e t e _ e d a ()
21
Chapter-5
Data Visualization and Storytelling
Gestalt Heuristics
Gestalt principles describe how humans naturally perceive visual elements as organized patterns and wholes.
1
2
Avoid Chartjunk
Chartjunk includes unnecessary visual elements that distract from the data:
• Heavy grid lines
• Unnecessary 3D effects
• Excessive decorations or patterns
2
0.2. VISUALIZATION WITH MATPLOTLIB: LINE, BAR, HISTOGRAM, SCATTER, SUBPLOTS 3
Object-Oriented Interface
• Provides more control and customization
• Explicit creation of Figure and Axes objects
• Better for complex plots and subplots
• Recommended for production code and reusability
Figure
Title
Axes
Plot Area
X-axis Label
Y-axis Label
Bar Plot
• Purpose: Compare quantities across categories
• Types: Vertical, horizontal, stacked, grouped
• When to use: Frequency counts, categorical comparison, survey responses
3
4
Histogram
• Purpose: Show distribution of continuous variables
• Key concepts:
Scatter Plot
• Purpose: Show relationship between two continuous variables
• Variations: Bubble charts (size as third variable), colored scatter (categorical data as color)
Subplots
• Purpose: Display multiple plots in one figure
Common formats:
4
0.4. PROGRAM 2: BAR PLOTS 5
• Line Plot with Markers: Adds markers to highlight individual data points.
• Multiple Lines with Styles: Shows how to plot several lines with varying linestyles and random noise.
• Fill Between (Area Plot): Illustrates how to display a confidence interval or range around a line using fill between.
5
6
• Horizontal Bar Plot: Useful when category names are long, easier to read.
• Grouped Bar Plot: Compares sales across two years side by side for each product.
• Stacked Bar Plot: Displays cumulative totals by stacking 2024 sales on top of 2023.
• Bar Plot with Error Bars: Includes variability or uncertainty in the data using error bars.
6
0.5. PROGRAM 3: HISTOGRAMS 7
1 # 5. Cumulative Histogram
2 plt . figure ( figsize =(10 , 6) )
3 plt . hist ( normal_data , bins =30 , cumulative = True , density = True ,
4 color = ’ steelblue ’ , edgecolor = ’ black ’ , alpha =0.7 ,
5 label = ’ Cumulative Distribution ’)
6 plt . xlabel ( ’ Values ’)
7 plt . ylabel ( ’ Cumulative Probability ’)
8 plt . title ( ’ Cumulative Histogram ( CDF ) ’)
9 plt . grid ( True , alpha =0.3)
10 plt . show ()
Listing 14: Cumulative Histogram
1 # 6. 2 D Histogram ( Hexbin )
2 x = np . random . normal (0 , 1 , 5000)
3 y = x + np . random . normal (0 , 0.5 , 5000)
4
5 plt . figure ( figsize =(10 , 8) )
6 plt . hexbin (x , y , gridsize =30 , cmap = ’ YlOrRd ’)
7 plt . colorbar ( label = ’ Count ’)
8 plt . xlabel ( ’X values ’)
9 plt . ylabel ( ’Y values ’)
10 plt . title ( ’2 D Histogram ( Hexbin Plot ) ’)
11 plt . show ()
Listing 15: 2D Histogram (Hexbin Plot)
• Different Bin Sizes: Demonstrates how bin width affects histogram appearance and interpretation.
• Histogram with KDE Overlay: Adds smooth density curve to highlight distribution shape.
7
8
1 import numpy as np
2 import matplotlib . pyplot as plt
3
4 # Generate positively correlated data
5 np . random . seed (42)
6 n = 200
7 x = np . random . normal (0 , 1 , n )
8 y = 0.7 * x + np . random . normal (0 , 0.5 , n )
9
10 # 1. Basic Scatter Plot
11 plt . figure ( figsize =(10 , 6) )
12 plt . scatter (x , y , alpha =0.6 , color = ’ steelblue ’ , edgecolor = ’ black ’ , s =50)
13 plt . xlabel ( ’X variable ’)
14 plt . ylabel ( ’Y variable ’)
15 plt . title ( ’ Basic Scatter Plot - Positive Correlation ’)
16 plt . grid ( True , alpha =0.3)
17 plt . show ()
Listing 16: Basic Scatter Plot
1 # 3. Bubble Chart
2 n = 100
3 x = np . random . uniform (0 , 100 , n )
4 y = np . random . uniform (0 , 100 , n )
5 population = np . random . gamma (2 , 10 , n ) * 1000 # Bubble size
6 colors = np . random . uniform (0 , 1 , n ) # Color scale
7
8 scatter = plt . scatter (x , y , s = population /100 , c = colors , alpha =0.6 ,
9 cmap = ’ viridis ’ , edgecolor = ’ black ’ , linewidth =0.5)
10 plt . colorbar ( scatter , label = ’ Color scale ’)
11 plt . xlabel ( ’X coordinate ’)
12 plt . ylabel ( ’Y coordinate ’)
13 plt . title ( ’ Bubble Chart - Size = Population , Color = Category ’)
14 plt . grid ( True , alpha =0.3)
15 plt . show ()
Listing 18: Bubble Chart (Size
5 # Regression line
6 z = np . polyfit (x , y , 1)
7 p = np . poly1d ( z )
8 x_line = np . linspace ( x . min () , x . max () , 100)
9 plt . plot ( x_line , p ( x_line ) , ’r - ’ , linewidth =2 ,
10 label = f ’ Regression line ( y ={ z [0]:.2 f } x +{ z [1]:.2 f }) ’)
11
12 plt . xlabel ( ’X variable ’)
13 plt . ylabel ( ’Y variable ’)
14 plt . title ( ’ Scatter Plot with Regression Line ’)
15 plt . legend ()
16 plt . grid ( True , alpha =0.3)
8
0.7. PROGRAM 5: SUBPLOTS 9
17 plt . show ()
Listing 19: Scatter Plot with Regression Line
• Bubble Chart: Introduces a third variable via bubble size and a fourth via color.
• Regression Line: Adds a trend line to indicate the linear relationship between variables.
1 import numpy as np
2 import matplotlib . pyplot as plt
3
4 # Generate sample data
5 x = np . linspace (0 , 10 , 100)
6 categories = [ ’A ’ , ’B ’ , ’C ’ , ’D ’]
7 values = [23 , 45 , 56 , 78]
8 data1 = np . random . normal (0 , 1 , 1000)
9 data2 = np . random . normal (2 , 1.5 , 1000)
10
11 # 1. Basic Subplot (2 x2 grid )
12 fig , axes = plt . subplots (2 , 2 , figsize =(12 , 10) )
13
14 # Line plot
15 axes [0 , 0]. plot (x , np . sin ( x ) , ’b - ’ , linewidth =2)
16 axes [0 , 0]. set_title ( ’ Line Plot - sin ( x ) ’)
17 axes [0 , 0]. set_xlabel ( ’x ’)
18 axes [0 , 0]. set_ylabel ( ’ sin ( x ) ’)
19 axes [0 , 0]. grid ( True , alpha =0.3)
20
21 # Bar plot
22 axes [0 , 1]. bar ( categories , values , color = ’ steelblue ’ , edgecolor = ’ black ’)
23 axes [0 , 1]. set_title ( ’ Bar Plot ’)
24 axes [0 , 1]. set_xlabel ( ’ Categories ’)
25 axes [0 , 1]. set_ylabel ( ’ Values ’)
26 axes [0 , 1]. grid ( True , alpha =0.3 , axis = ’y ’)
27
28 # Histogram
29 axes [1 , 0]. hist ( data1 , bins =30 , alpha =0.7 , color = ’ green ’ , edgecolor = ’ black ’)
30 axes [1 , 0]. set_title ( ’ Histogram ’)
31 axes [1 , 0]. set_xlabel ( ’ Values ’)
32 axes [1 , 0]. set_ylabel ( ’ Frequency ’)
33 axes [1 , 0]. grid ( True , alpha =0.3 , axis = ’y ’)
34
35 # Scatter plot
36 axes [1 , 1]. scatter ( data1 , data2 , alpha =0.5 , color = ’ coral ’ , edgecolor = ’ black ’)
37 axes [1 , 1]. set_title ( ’ Scatter Plot ’)
38 axes [1 , 1]. set_xlabel ( ’X values ’)
39 axes [1 , 1]. set_ylabel ( ’Y values ’)
40 axes [1 , 1]. grid ( True , alpha =0.3)
41
9
10
7 ax1 . plot (x , np . sin ( x ) , ’b - ’ , linewidth =2) ; ax1 . set_title ( ’ sin ( x ) ’) ; ax1 . grid ( True , alpha =0.3)
8 ax2 . plot (x , np . cos ( x ) , ’r - ’ , linewidth =2) ; ax2 . set_title ( ’ cos ( x ) ’) ; ax2 . grid ( True , alpha =0.3)
9 ax3 . plot (x , np . sin ( x ) * np . cos ( x ) , ’g - ’ , linewidth =2) ; ax3 . set_title ( ’ sin ( x ) cos ( x ) ’) ; ax3 . set_xlabel (
’x ’) ; ax3 . grid ( True , alpha =0.3)
10
11 plt . suptitle ( ’ Irregular Subplot Layout ’ , fontsize =16)
12 plt . tight_layout ()
13 plt . show ()
Listing 22: Irregular Subplot Layout
12 ax1 . plot (x , np . sin ( x ) , ’b - ’ , linewidth =2) ; ax1 . set_title ( ’ Wide Plot ( Top ) ’) ; ax1 . grid ( True , alpha =0.3)
13 ax2 . plot (x , np . cos ( x ) , ’r - ’ , linewidth =2) ; ax2 . set_title ( ’ Medium Plot ’) ; ax2 . grid ( True , alpha =0.3)
14 ax3 . barh ( categories , values , color = ’ steelblue ’) ; ax3 . set_title ( ’ Tall Plot ’) ; ax3 . grid ( True , alpha =0.3 ,
axis = ’x ’)
15 ax4 . hist ( data1 , bins =30 , color = ’ green ’ , edgecolor = ’ black ’) ; ax4 . set_title ( ’ Small Plot 1 ’) ; ax4 . grid (
True , alpha =0.3)
16 ax5 . scatter ( data1 , data2 , alpha =0.5 , color = ’ coral ’) ; ax5 . set_title ( ’ Small Plot 2 ’) ; ax5 . grid ( True ,
alpha =0.3)
17
18 plt . suptitle ( ’ Complex GridSpec Layout ’ , fontsize =16)
19 plt . tight_layout ()
20 plt . show ()
Listing 24: Nested Subplots Using GridSpec
• Nested Subplots/GridSpec: Provides full control over layout and subplot sizes.
1 import numpy as np
2 import matplotlib . pyplot as plt
3
4 # Generate data
5 x = np . linspace (0 , 10 , 50)
6 y1 = 2* x + 1 + np . random . normal (0 , 1 , 50)
7 y2 = 3* x - 2 + np . random . normal (0 , 1.5 , 50)
8
10
0.9. PROGRAM 6: COMPLETE CUSTOMIZATION EXAMPLE 11
14 ax . scatter (x , y2 , color = ’ red ’ , marker = ’s ’ , s =80 , alpha =0.7 , edgecolor = ’ black ’ , linewidth =1 , label = ’
Dataset B ’)
15
16 # Regression lines
17 z1 = np . polyfit (x , y1 , 1)
18 z2 = np . polyfit (x , y2 , 1)
19 p1 = np . poly1d ( z1 )
20 p2 = np . poly1d ( z2 )
21 ax . plot (x , p1 ( x ) , ’b - - ’ , linewidth =2 , label = f ’A trend ( slope ={ z1 [0]:.2 f }) ’)
22 ax . plot (x , p2 ( x ) , ’r - - ’ , linewidth =2 , label = f ’B trend ( slope ={ z2 [0]:.2 f }) ’)
23
24 # Axis customization
25 ax . set_xlabel ( ’ Time ( hours ) ’ , fontsize =12 , fontweight = ’ bold ’)
26 ax . set_ylabel ( ’ Measurement ( units ) ’ , fontsize =12 , fontweight = ’ bold ’)
27 ax . set_title ( ’ Complete Customized Plot Example \ nwith All Elements ’ , fontsize =16 , fontweight = ’ bold ’ , pad
=20)
28 ax . set_xlim ( -0.5 , 10.5)
29 ax . set_ylim ( -5 , 35)
30 ax . set_xticks ( np . arange (0 , 11 , 1) )
31 ax . set_yticks ( np . arange ( -5 , 36 , 5) )
32 ax . tick_params ( axis = ’ both ’ , which = ’ major ’ , labelsize =10)
33
34 # Grid , legend , and annotations
35 ax . grid ( True , alpha =0.3 , linestyle = ’ -- ’ , linewidth =0.5)
36 ax . legend ( loc = ’ upper left ’ , fontsize =10 , framealpha =0.9)
37 ax . annotate ( ’ Important \ nobservation ’ , xy =(5 , 15) , xytext =(6 , 25) ,
38 arrowprops = dict ( facecolor = ’ black ’ , shrink =0.05 , width =1.5) ,
39 fontsize =11 , ha = ’ center ’ , bbox = dict ( boxstyle = ’ round ’ , facecolor = ’ wheat ’ , alpha =0.5) )
40 ax . text (0.02 , 0.98 , ’ Correlation : 0.85 ’ , transform = ax . transAxes ,
41 fontsize =11 , ve rtical alignm ent = ’ top ’ ,
42 bbox = dict ( boxstyle = ’ round ’ , facecolor = ’ lightblue ’ , alpha =0.8) )
43
44 # Horizontal and vertical lines
45 ax . axhline ( y =0 , color = ’ gray ’ , linestyle = ’ - ’ , linewidth =0.5 , alpha =0.5)
46 ax . axvline ( x =5 , color = ’ gray ’ , linestyle = ’ - ’ , linewidth =0.5 , alpha =0.5)
47
48 # Secondary y - axis
49 ax2 = ax . twinx ()
50 ax2 . set_ylabel ( ’ Secondary Scale ’ , fontsize =12 , color = ’ purple ’)
51 ax2 . tick_params ( axis = ’y ’ , labelcolor = ’ purple ’)
52 ax2 . set_ylim ( -10 , 70)
53
54 plt . tight_layout ()
55 plt . show ()
Listing 25: Complete Customization with Scatter Plots and Regression Lines
• Annotations and text boxes: Highlight important observations and display contextual information.
11
12
8
9 # Create figure and axis
10 fig , ax = plt . subplots ( figsize =(12 , 8) )
11
12 # Scatter plots
13 ax . scatter (x , y1 , color = ’ blue ’ , marker = ’o ’ , s =80 , alpha =0.7 , edgecolor = ’ black ’ , linewidth =1 , label = ’
Dataset A ’)
14 ax . scatter (x , y2 , color = ’ red ’ , marker = ’s ’ , s =80 , alpha =0.7 , edgecolor = ’ black ’ , linewidth =1 , label = ’
Dataset B ’)
15
16 # Regression lines
17 z1 = np . polyfit (x , y1 , 1)
18 z2 = np . polyfit (x , y2 , 1)
19 p1 = np . poly1d ( z1 )
20 p2 = np . poly1d ( z2 )
21 ax . plot (x , p1 ( x ) , ’b - - ’ , linewidth =2 , label = f ’A trend ( slope ={ z1 [0]:.2 f }) ’)
22 ax . plot (x , p2 ( x ) , ’r - - ’ , linewidth =2 , label = f ’B trend ( slope ={ z2 [0]:.2 f }) ’)
23
24 # Axis customization
25 ax . set_xlabel ( ’ Time ( hours ) ’ , fontsize =12 , fontweight = ’ bold ’)
26 ax . set_ylabel ( ’ Measurement ( units ) ’ , fontsize =12 , fontweight = ’ bold ’)
27 ax . set_title ( ’ Complete Customized Plot Example \ nwith All Elements ’ , fontsize =16 , fontweight = ’ bold ’ , pad
=20)
28 ax . set_xlim ( -0.5 , 10.5)
29 ax . set_ylim ( -5 , 35)
30 ax . set_xticks ( np . arange (0 , 11 , 1) )
31 ax . set_yticks ( np . arange ( -5 , 36 , 5) )
32 ax . tick_params ( axis = ’ both ’ , which = ’ major ’ , labelsize =10)
33
34 # Grid , legend , and annotations
35 ax . grid ( True , alpha =0.3 , linestyle = ’ -- ’ , linewidth =0.5)
36 ax . legend ( loc = ’ upper left ’ , fontsize =10 , framealpha =0.9)
37 ax . annotate ( ’ Important \ nobservation ’ , xy =(5 , 15) , xytext =(6 , 25) ,
38 arrowprops = dict ( facecolor = ’ black ’ , shrink =0.05 , width =1.5) ,
39 fontsize =11 , ha = ’ center ’ , bbox = dict ( boxstyle = ’ round ’ , facecolor = ’ wheat ’ , alpha =0.5) )
40 ax . text (0.02 , 0.98 , ’ Correlation : 0.85 ’ , transform = ax . transAxes ,
41 fontsize =11 , ve rtical alignm ent = ’ top ’ ,
42 bbox = dict ( boxstyle = ’ round ’ , facecolor = ’ lightblue ’ , alpha =0.8) )
43
44 # Horizontal and vertical lines
45 ax . axhline ( y =0 , color = ’ gray ’ , linestyle = ’ - ’ , linewidth =0.5 , alpha =0.5)
46 ax . axvline ( x =5 , color = ’ gray ’ , linestyle = ’ - ’ , linewidth =0.5 , alpha =0.5)
47
48 # Secondary y - axis
49 ax2 = ax . twinx ()
50 ax2 . set_ylabel ( ’ Secondary Scale ’ , fontsize =12 , color = ’ purple ’)
51 ax2 . tick_params ( axis = ’y ’ , labelcolor = ’ purple ’)
52 ax2 . set_ylim ( -10 , 70)
53
54 plt . tight_layout ()
55 plt . show ()
Listing 26: Complete Customization with Scatter Plots and Regression Lines
• Annotations and text boxes: Highlight important observations and display contextual information.
5.3 Seaborn for Statistical Visualization: Box Plot, Pair Plot, Heat Map
1. Introduction to Seaborn
Seaborn is a Python data visualization library based on Matplotlib that provides a high-level interface for drawing statistical
graphics. It integrates closely with pandas DataFrames and offers sensible defaults for statistical visualizations.
Key Features:
12
0.9. PROGRAM 6: COMPLETE CUSTOMIZATION EXAMPLE 13
3. Box Plot
3.1 What is a Box Plot?
A box plot displays the distribution of data based on a five-number summary: minimum, Q1, median, Q3, and maximum.
Useful for identifying outliers and comparing distributions across categories.
• Identifying outliers
• Detecting skewness
Function Purpose
[Link]() Standard box plot
[Link]() Letter-value plot for large datasets
[Link]() Box plot + kernel density estimate
[Link]() Mean with error bars
1 sns . boxplot (
2 x = None , y = None , hue = None , data = None ,
3 order = None , palette = None , showfliers = True
4 )
4. Pair Plot
4.1 What is a Pair Plot?
Creates a grid showing pairwise relationships, with scatter plots for numeric pairs and distributions on the diagonal.
13
14
• Identifying relationships
• Checking multicollinearity
4.5 Limitations
• Cluttered with many variables (¿7)
• Computationally intensive
1 sns . pairplot (
2 data , hue = None , vars = None ,
3 kind = ’ scatter ’ , diag_kind = ’ hist ’ ,
4 palette = None , markers = None ,
5 height =2.5 , aspect =1 , corner = False
6 )
5. Heat Map
5.1 What is a Heat Map?
Graphical representation of data where values are shown by color, useful for matrices (correlation, confusion, etc.)
5.2 Structure
• Grid cells: values
• Row/column labels
• Optional annotations
Application Purpose
Correlation Matrix Show variable relationships
Confusion Matrix Classification performance
Missing Data Pattern Show missing data
Time Series Patterns across time
Genomic Data Gene expression patterns
• Confusion matrices
• Identifying clusters
14
0.9. PROGRAM 6: COMPLETE CUSTOMIZATION EXAMPLE 15
1 sns . heatmap (
2 data , annot = False , fmt = ’ .2 g ’ , cmap = ’ coolwarm ’ ,
3 center = None , vmin = None , vmax = None , cbar = True ,
4 square = False , linewidths =0 , linecolor = ’ white ’ ,
5 xticklabels = ’ auto ’ , yticklabels = ’ auto ’ ,
6 mask = None , ax = None
7 )
15
16
3.2 Traces
A trace is the fundamental building block of a figure. Each trace represents one dataset and its visualization. Multiple traces
can be combined in one figure.
Common trace types:
Best for:
Best for:
• Fine-tuned customization
• With Dash: Plotly provides the visualization foundation for interactive web apps
7. Customization Capabilities
7.1 Visual Styling
• Templates: ’plotly’, ’plotlyd ark ′ ,′ ggplot2′ ,′ seaborn′ ,′ simplew hite′
• Color scales: sequential, diverging, qualitative
16
0.9. PROGRAM 6: COMPLETE CUSTOMIZATION EXAMPLE 17
Stage Description
Data Raw information collected from various sources
Visualization Graphical representation of data
Pattern Recognition Identifying trends, clusters, outliers
Hypothesis Formulating explanations for patterns
Insight Deep understanding with business implications
Action Decisions based on insights
17
18
Answers ”What should we do?” combining patterns with domain knowledge. Examples:
4.2 Anomalies
4.3 Comparisons
18
0.9. PROGRAM 6: COMPLETE CUSTOMIZATION EXAMPLE 19
Question Visualization
”How has this changed over time?” Line chart
”How do categories compare?” Bar chart
”What is the distribution?” Histogram, box plot
”What is the relationship?” Scatter plot, heatmap
”What is the composition?” Stacked bar, treemap
19
20
• order id
• order date
• customer id
• product category
• revenue
• cost
• country
• acquisition channel
3 # Load data
4 df = pd . read_csv ( ’ sales_data . csv ’)
5
6 # Overview
7 df . info ()
8 df . describe ()
9
14 # Remove duplicates
15 df . drop_duplicates ( subset = ’ order_id ’ , inplace = True )
16
20
0.9. PROGRAM 6: COMPLETE CUSTOMIZATION EXAMPLE 21
• Univariate Analysis: Histograms and box plots for revenue and cost to detect outliers.
• Feature Engineering:
4 # Total Profit
5 df [ ’ profit ’] = df [ ’ revenue ’] - df [ ’ cost ’]
6 total_profit = df [ ’ profit ’ ]. sum ()
7
11 # Profit Margin
12 profit_margin = ( total_profit / total_revenue ) * 100
6 # Sidebar filters
7 start_date = st . sidebar . date_input ( " Start Date " )
8 end_date = st . sidebar . date_input ( " End Date " )
9 category_filter = st . sidebar . multiselect ( " Category " , df [ ’ product_category ’ ]. unique () )
10
11 # KPI cards
12 st . metric ( " Total Revenue " , f " $ { total_revenue : ,.2 f } " )
13 st . metric ( " Total Profit " , f " $ { total_profit : ,.2 f } " )
14 st . metric ( " Profit Margin " , f " { profit_margin :.2 f }% " )
15
16 # Visualizations
17 fig_line = px . line ( df , x = ’ order_date ’ , y = ’ revenue ’ , title = ’ Monthly Revenue ’)
18 st . plotly_chart ( fig_line )
21
Chapter-6
Data Engineering and Automation
• Data Acquisition: Getting raw data from various sources such as databases, files, APIs, or streams.
• Data Preparation: Cleaning, transforming, and structuring data for analysis. This often represents the bulk of data
engineering work.
• Data Modeling & Analysis: The core work of a data scientist, which relies entirely on the quality of the prepared
data.
• Deployment & Monitoring: Putting models into production, which requires robust and reliable data pipelines to feed
the system.
• Data Pipelines and ETL/ELT: Moving data from source systems to destinations for analysis.
• Data Wrangling: Hands-on cleaning and transforming of raw data into structured, usable formats. Includes:
• Data Automation: Using technology to execute repetitive tasks without manual intervention.
• Data Engineer:
• Data Scientist:
The handoff between these roles is a critical point in any project. A well-structured data engineering foundation allows data
scientists to focus on core competencies without being bogged down by data acquisition and cleaning.
1
2
Extraction Strategies:
• pandas (read csv, read json, read excel) for file ingestion
• Data cleaning: Remove duplicates, handle missing values (drop duplicates(), fillna(), dropna())
Transformation Approaches:
Load Strategies:
2
3
• Use case: ETL for limited warehouse power, ELT for cloud warehouses
• Data Quality Validation: Schema checks, completeness, uniqueness, range, and count validation
• Error Handling and Logging: Comprehensive logs, defensive reading, isolate bad data, alerting
• Incremental Processing: Process only new/changed data, track timestamps, ensure idempotency
• Orchestration: Use tools like Apache Airflow, Prefect, or cloud-native services for scheduling and dependency manage-
ment
3
4
27 ’ numeric_column ’: 0 ,
28 ’ cat eg or ic al _c ol um n ’: ’ Unknown ’
29 })
30
31 # Standardize date formats
32 df [ ’ date ’] = pd . to_datetime ( df [ ’ date ’] , errors = ’ coerce ’)
33
34 # Filter invalid records
35 df = df [ df [ ’ value ’] > 0]
36
37 # Create derived columns
38 df [ ’ processed_date ’] = datetime . now ()
39
40 logger . info ( f " Transformation complete . { len ( df ) } rows remain " )
41 return df
42
43 def validate_data ( df ) :
44 " " " Run quality checks " " "
45 logger . info ( " Validating data " )
46
47 # Schema validation
48 expected_columns = [ ’ id ’ , ’ date ’ , ’ value ’ , ’ category ’]
49 assert all ( col in df . columns for col in expected_columns ) , " Missing columns "
50
51 # Completeness check
52 null_counts = df . isnull () . sum ()
53 logger . info ( f " Null counts : { null_counts } " )
54
55 # Uniqueness check
56 assert df [ ’ id ’ ]. is_unique , " Duplicate IDs found "
57
58 logger . info ( " Validation passed " )
59 return True
60
61 def load_data ( df , output_path ) :
62 " " " Load to destination " " "
63 logger . info ( f " Loading { len ( df ) } rows to { output_path } " )
64 df . to_parquet ( output_path , index = False )
65 logger . info ( " Load complete " )
66
67 def run_pipeline ( input_file , output_file ) :
68 " " " Orchestrate the full ETL process " " "
69 try :
70 logger . info ( " Starting ETL pipeline " )
71
72 # Extract
73 data = extract_data ( input_file )
74
75 # Transform
76 transformed = transform_data ( data )
77
78 # Validate
79 validate_data ( transformed )
80
81 # Load
82 load_data ( transformed , output_file )
83
84 logger . info ( " Pipeline completed successfully " )
85
86 except Exception as e :
87 logger . error ( f " Pipeline failed : { str ( e ) } " )
88 raise
89
90 # Execute pipeline
91 if __name__ == " __main__ " :
92 run_pipeline ( " data / raw / input . csv " , " data / curated / output . parquet " )
Extract Phase
• Reads data from a CSV file (can be JSON, Excel, API, etc.)
Transform Phase
• Removes duplicates
4
5
Validation Phase
• Function: validate data(df)
Load Phase
• Function: load data(df, output path)
Pipeline Orchestration
• Function: run pipeline(input file, output file)
8. Scaling Considerations
• Pandas: For datasets that fit in memory (up to a few GB)
10. Summary
• ETL Definition: Extract, Transform, Load – the three phases of data movement
5
6
Output example:
• MINUTE: 0-59
• HOUR: 0-23
30 04 * * * /var/www/websites/[Link]
• 0 9-17/2 * * 1-5 /path/to/[Link] : Run every second hour between 9 AM-5 PM, Monday-Friday
6
0.1. SCHEDULE LIBRARY FOR WORKFLOW AUTOMATION 7
Scheduler
The scheduler manages all registered jobs and determines when each task should be executed according to its schedule.
Job Runner
The run pending() function checks scheduled jobs and executes those whose execution time has arrived.
1 import schedule
2 import time
3
4 def my_task () :
5 print ( " Task executed " )
6
7 # Schedule the task
8 schedule . every (10) . minutes . do ( my_task )
9
10 # Keep the scheduler running
11 while True :
12 schedule . run_pending ()
13 time . sleep (1)
Minutes
Hours
Days
Weeks
7
8
1 # Specific weekdays
2 schedule . every () . monday . do ( task )
3 schedule . every () . tuesday . do ( task )
4 schedule . every () . wednesday . do ( task )
5 schedule . every () . thursday . do ( task )
6 schedule . every () . friday . do ( task )
7 schedule . every () . saturday . do ( task )
8 schedule . every () . sunday . do ( task )
9
10 # Weekdays only ( Monday to Friday )
11 schedule . every () . monday . to . friday . do ( task )
12
13 # Multiple days
14 schedule . every () . monday . and . wednesday . do ( task )
1. Logging Fundamentals
Python’s Logging Module
Python provides a built-in logging module that offers a flexible framework for generating log messages from applications.
Logging Levels
Python defines five standard logging levels arranged by increasing severity:
• CRITICAL (50) – A severe error indicating a system-level failure requiring immediate attention.
1 import logging
2
3 # Basic configuration
4 logging . basicConfig (
5 level = logging . INFO ,
6 format = ’ %( asctime ) s - %( name ) s - %( levelname ) s - %( message ) s ’ ,
7 filename = ’ pipeline . log ’ ,
8 filemode = ’a ’
9 )
10
11 # Create different types of logs
12 logging . debug ( " Variable has value : % s " , path )
13 logging . info ( " Data has been transformed and will now be loaded " )
14 logging . warning ( " Unexpected number of rows detected : % d " , row_count )
15 logging . error ( " KeyError arose in execution : % s " , str ( e ) )
Example output:
8
0.1. SCHEDULE LIBRARY FOR WORKFLOW AUTOMATION 9
9
10
10
0.1. SCHEDULE LIBRARY FOR WORKFLOW AUTOMATION 11
4. Retry Mechanisms
Basic Retry Logic
Retry mechanisms are used to handle temporary or transient failures such as network timeouts, API rate limits, or temporary
service unavailability. A common approach is to retry the operation multiple times with an increasing delay between attempts,
known as exponential backoff.
1 import time
2 import logging
3
10 Args :
11 operation : Callable to retry
12 max_retries : Maximum number of retry attempts
13 initial_delay : Initial delay in seconds ( doubles each retry )
14 """
15 for attempt in range ( max_retries ) :
16 try :
17 logger . info ( f " Attempt { attempt + 1}/{ max_retries } " )
18 return operation ()
19
20 except Exception as e :
21 wait_time = initial_delay * (2 ** attempt )
22
23 if attempt < max_retries - 1:
24 logger . warning (
25 f " Attempt { attempt + 1} failed : { str ( e ) }. "
26 f " Retrying in { wait_time } seconds ... "
27 )
28 time . sleep ( wait_time )
29 else :
30 logger . error ( f " All { max_retries } attempts failed " )
31 raise # Re - raise on final failure
32
33
34 # Usage example
35 def uns ta bl e_ op er at io n () :
36 # Simulate flaky operation
37 if random . random () < 0.7: # 70% failure rate
38 raise ConnectionError ( " Network timeout " )
39 return " Success "
40
41
42 try :
43 result = retry_operation ( unstable_operation , max_retries =5)
44 logger . info ( f " Operation succeeded : { result } " )
45 except Exception as e :
46 logger . error ( f " Operation failed after retries : { e } " )
11
12
14 @retry (
15 stop = sto p_ af te r_ at te mp t (5) ,
16 wait = wait_exponential ( multiplier =1 , min =4 , max =30) ,
17 retry = r e t r y _ i f _ e x c e p t i o n _ t y p e ((
18 requests . exceptions . Timeout ,
19 requests . exceptions . ConnectionError
20 )),
21 before = before_log ( logger , logging . INFO ) ,
22 after = after_log ( logger , logging . INFO )
23 )
24 def f et c h _d a t a_ f r om _ a pi ( url ) :
25 " " " Fetch data with automatic retry for transient failures " " "
26 logger . info ( f " Fetching data from { url } " )
27 response = requests . get ( url , timeout =5)
28 response . raise_for_status ()
29 return response . json ()
30
31 # Usage
32 try :
33 data = f et c h _d a t a_ f r om _ a pi ( " https :// api . example . com / data " )
34 except Exception as e :
35 logger . error ( f " API request failed after retries : { e } " )
32 return result
33
34 except Exception as e :
35 end_time = time . time ()
36 duration = end_time - start_time
37
38 logger . error (
39 f " Failed { func . __name__ } after { duration :.2 f } seconds : { str ( e ) } "
40 )
41 record_metric ( f " { func . __name__ }. duration " , duration )
42 record_metric ( f " { func . __name__ }. success " , 0)
43 raise
44
45 return wrapper
46
47
48 @m onitor _pipel ine
49 def etl_pipeline () :
50 # Pipeline logic here
51 pass
12
0.1. SCHEDULE LIBRARY FOR WORKFLOW AUTOMATION 13
6. Alerting Mechanisms
Alerting mechanisms notify engineers when a pipeline fails or encounters unusual conditions. Alerts can be delivered through
email, messaging platforms, or monitoring systems.
1 import smtplib
2 import requests
3 from email . mime . text import MIMEText
4 from email . mime . multipart import MIMEMultipart
5
6 class AlertManager :
7 def __init__ ( self , config ) :
8 self . config = config
9 self . logger = logging . getLogger ( __name__ )
10
13
14
29 )
30 server . send_message ( msg )
31 server . quit ()
32
33 self . logger . info ( f " Email alert sent : { subject } " )
34
35 except Exception as e :
36 self . logger . error ( f " Failed to send email alert : { e } " )
37
38 def send_slack_alert ( self , message , severity = ’ ERROR ’) :
39 " " " Send Slack alert via webhook " " "
40 try :
41 webhook_url = self . config [ ’ slack_webhook ’]
42
43 color = {
44 ’ INFO ’: ’ #36 a64f ’ ,
45 ’ WARNING ’: ’# ffcc00 ’ ,
46 ’ ERROR ’: ’# ff0000 ’ ,
47 ’ CRITICAL ’: ’ #8 b0000 ’
48 }. get ( severity , ’ #808080 ’)
49
50 payload = {
51 ’ attachments ’: [{
52 ’ color ’: color ,
53 ’ title ’: f ’ Pipeline Alert : { severity } ’ ,
54 ’ text ’: message ,
55 ’ fields ’: [
56 {
57 ’ title ’: ’ Pipeline ’ ,
58 ’ value ’: self . config . get ( ’ pipeline_name ’ , ’ unknown ’) ,
59 ’ short ’: True
60 },
61 {
62 ’ title ’: ’ Time ’ ,
63 ’ value ’: datetime . now () . strftime (
64 ’%Y -% m -% d % H :% M :% S ’
65 ),
66 ’ short ’: True
67 }
68 ]
69 }]
70 }
71
72 response = requests . post ( webhook_url , json = payload )
73 response . raise_for_status ()
74
75 self . logger . info (
76 f " Slack alert sent : { message [:50]}... "
77 )
78
79 except Exception as e :
80 self . logger . error (
81 f " Failed to send Slack alert : { e } "
82 )
83
84
85 # Usage
86 alert_config = {
87 ’ email_from ’: ’ pipeline@example . com ’ ,
88 ’ email_to ’: ’ team@example . com ’ ,
89 ’ smtp_server ’: ’ smtp . gmail . com ’ ,
90 ’ smtp_port ’: 587 ,
91 ’ smtp_user ’: ’ user@gmail . com ’ ,
92 ’ smtp_password ’: ’ password ’ ,
93 ’ slack_webhook ’: ’ https :// hooks . slack . com / services / xxx / yyy / zzz ’ ,
94 ’ pipeline_name ’: ’ daily_etl ’
95 }
96
97 alerts = AlertManager ( alert_config )
98
99 try :
100 # Pipeline execution
101 pass
102
103 except Exception as e :
104 alerts . send_slack_alert (
105 f " Pipeline failed : { str ( e ) } " ,
106 severity = ’ ERROR ’
107 )
108 alerts . send_email_alert (
109 " Pipeline Failure Alert " ,
110 f " The daily ETL pipeline failed with error :\ n \ n { str ( e ) } "
111 )
1 import logging
2 import logging . config
3 import time
14
0.1. SCHEDULE LIBRARY FOR WORKFLOW AUTOMATION 15
4 import json
5 from datetime import datetime
6 from tenacity import retry , stop_after_attempt , wait_exponential
7 import pandas as pd
8 import requests
9
10 # Configure logging
11 LOGGING_CONFIG = {
12 ’ version ’: 1 ,
13 ’ formatters ’: {
14 ’ detailed ’: {
15 ’ format ’: ’ %( asctime ) s - %( name ) s - %( levelname ) s - %( message ) s ’
16 },
17 ’ json ’: {
18 ’ () ’: ’ pipeline_utils . JSONFormatter ’
19 }
20 },
21 ’ handlers ’: {
22 ’ console ’: {
23 ’ class ’: ’ logging . StreamHandler ’ ,
24 ’ level ’: ’ INFO ’ ,
25 ’ formatter ’: ’ detailed ’
26 },
27 ’ file ’: {
28 ’ class ’: ’ logging . handlers . R ot a t in g F il e H an d l er ’ ,
29 ’ level ’: ’ DEBUG ’ ,
30 ’ formatter ’: ’ json ’ ,
31 ’ filename ’: ’ pipeline . log ’ ,
32 ’ maxBytes ’: 10485760 ,
33 ’ backupCount ’: 5
34 },
35 ’ error_file ’: {
36 ’ class ’: ’ logging . handlers . R ot a t in g F il e H an d l er ’ ,
37 ’ level ’: ’ ERROR ’ ,
38 ’ formatter ’: ’ json ’ ,
39 ’ filename ’: ’ errors . log ’ ,
40 ’ maxBytes ’: 10485760 ,
41 ’ backupCount ’: 3
42 }
43 },
44 ’ loggers ’: {
45 ’ etl_pipeline ’: {
46 ’ handlers ’: [ ’ console ’ , ’ file ’ , ’ error_file ’] ,
47 ’ level ’: ’ DEBUG ’ ,
48 ’ propagate ’: False
49 }
50 }
51 }
52
53 logging . config . dictConfig ( LOGGING_CONFIG )
54 logger = logging . getLogger ( ’ etl_pipeline ’)
55
56 class PipelineMetrics :
57 " " " Track pipeline execution metrics " " "
58
59 def __init__ ( self , pipeline_name ) :
60 self . pipeline_name = pipeline_name
61 self . metrics = {
62 ’ pipeline ’: pipeline_name ,
63 ’ run_id ’: datetime . now () . strftime ( ’% Y % m % d_ % H % M % S ’) ,
64 ’ start_time ’: None ,
65 ’ end_time ’: None ,
66 ’ duration ’: None ,
67 ’ status ’: ’ running ’ ,
68 ’ stages ’: {} ,
69 ’ errors ’: []
70 }
71 self . logger = logging . getLogger ( ’ etl_pipeline . metrics ’)
72
73 def start_stage ( self , stage_name ) :
74 self . metrics [ ’ stages ’ ][ stage_name ] = {
75 ’ start_time ’: time . time () ,
76 ’ status ’: ’ running ’
77 }
78 self . logger . info ( f " Starting stage : { stage_name } " )
79
80 def end_stage ( self , stage_name , status = ’ success ’ , rows_processed = None ) :
81 stage = self . metrics [ ’ stages ’ ]. get ( stage_name , {})
82 if stage :
83 stage [ ’ end_time ’] = time . time ()
84 stage [ ’ duration ’] = stage [ ’ end_time ’] - stage . get ( ’ start_time ’ ,
85 stage [ ’ end_time ’ ])
86 stage [ ’ status ’] = status
87 if rows_processed :
88 stage [ ’ rows_processed ’] = rows_processed
89
90 self . logger . info (
91 f " Completed stage : { stage_name } - "
92 f " Duration : { stage [ ’ duration ’]:.2 f }s , Status : { status } "
93 )
94
95 def add_error ( self , stage , error ) :
96 error_record = {
97 ’ stage ’: stage ,
98 ’ timestamp ’: time . time () ,
99 ’ error_type ’: type ( error ) . __name__ ,
15
16
16
0.1. SCHEDULE LIBRARY FOR WORKFLOW AUTOMATION 17
202 try :
203 self . metrics . start_stage ( ’ extract ’)
204 data = self . extract ( self . config [ ’ source ’ ])
205 self . metrics . end_stage ( ’ extract ’)
206
207 self . metrics . start_stage ( ’ validate ’)
208 self . validate ( data )
209 self . metrics . end_stage ( ’ validate ’)
210
211 self . metrics . start_stage ( ’ transform ’)
212 data = self . transform ( data )
213 self . metrics . end_stage ( ’ transform ’)
214
215 self . metrics . start_stage ( ’ load ’)
216 self . load ( data , self . config [ ’ destination ’ ])
217 self . metrics . end_stage ( ’ load ’)
218
219 self . metrics . complete ( ’ success ’)
220 return data
221
222 except Exception as e :
223 self . metrics . add_error ( ’ pipeline ’ , e )
224 self . metrics . complete ( ’ failed ’)
225 raise
226
227
228 if __name__ == " __main__ " :
229
230 config = {
231 ’ name ’: ’ daily_sales_etl ’ ,
232 ’ source ’: ’ sales_data . csv ’ ,
233 ’ destination ’: ’ processed_sales . csv ’ ,
234 ’ required_columns ’: [ ’ order_id ’ , ’ customer_id ’ , ’ amount ’ , ’ date ’] ,
235 ’ critical_columns ’: [ ’ order_id ’ , ’ amount ’] ,
236 ’ max_null_pct ’: 10 ,
237 ’ alert_on_failure ’: True
238 }
239
240 pipeline = ETLPipeline ( config )
241
242 try :
243 result = pipeline . run ()
244 print ( " Pipeline executed successfully " )
245 except Exception as e :
246 print ( f " Pipeline failed : { e } " )
a) Data Warehouses
A data warehouse is a centralized repository designed for analytical processing (OLAP). It stores structured and processed data
optimized for reporting, dashboards, and business intelligence.
In traditional ETL pipelines, the data warehouse acts as the final destination. Data is extracted from operational systems,
transformed in staging environments, and then loaded into the warehouse using structured schemas such as star or snowflake
schemas. In ELT pipelines, raw data is first loaded into the warehouse and transformations are performed using SQL within
the warehouse itself.
Key Characteristics
• Optimized for Analytical Queries: Uses techniques such as indexing, partitioning, and columnar storage formats.
Common Platforms
Amazon Redshift, Google BigQuery, Snowflake, and Azure Synapse Analytics.
b) Data Lakes
A data lake is a large-scale storage repository designed to store raw data in its native format. Unlike warehouses, data lakes
support structured, semi-structured, and unstructured data.
In ELT architectures, data lakes often function as the initial landing zone where data from multiple sources is stored with
minimal transformation. This approach allows analysts and data scientists to process and analyze raw data later.
Key Characteristics
17
18
• Flexible Data Formats: Supports formats such as JSON, XML, images, and log files.
Common Platforms
AWS S3, Azure Data Lake Storage (ADLS), and Google Cloud Storage (GCS).
c) Data Lakehouses
A data lakehouse is a hybrid architecture that combines the flexibility of data lakes with the data management features of data
warehouses. This is achieved by adding a transactional metadata layer on top of data lake storage.
In pipeline architectures, the lakehouse can serve both as a landing zone for raw data and as an analytical storage system.
This reduces system complexity by eliminating the need for separate data lakes and warehouses.
Key Characteristics
• Open File Formats: Data is typically stored in open formats such as Parquet.
Common Technologies
Delta Lake, Apache Iceberg, and Apache Hudi.
Data Replication
Data replication involves maintaining multiple copies of data across systems or geographic locations.
Common uses include:
Data Caching
Caching stores intermediate or frequently accessed data in high-speed storage systems to reduce latency.
Typical use cases include:
This strategy helps organizations manage storage costs while maintaining data availability.
18
0.1. SCHEDULE LIBRARY FOR WORKFLOW AUTOMATION 19
16
17 # Write data partitioned by year and month
18 df . write \
19 . partitionBy ( " year " , " month " ) \
20 . mode ( " overwrite " ) \
21 . parquet ( " data_lake / sa les_pa rtitio ned " )
22
23 print ( " Data successfully written in partitioned format " )
19
20
54 }
55
56 pipeline = ETLPipeline ( etl_config ) # or elt_config
57 pipeline . extract ()
58 pipeline . transform ()
59 pipeline . load ()
• DataFrames as foundation: pandas DataFrames provide the structure for tabular data.
1 import pandas as pd
2 from datetime import datetime
3
4 def g e n e r a t e _ e x c e l _ r e p o r t ( data_path , output_path ) :
5 """
6 Generate formatted Excel report with multiple sheets
7 """
8 # Extract data
9 data = pd . read_csv ( data_path )
10
11 # Transform - create summary statistics
12 summary = pd . DataFrame ({
13 ’ Metric ’: [ ’ Total ’ , ’ Average ’ , ’ Maximum ’ , ’ Minimum ’] ,
14 ’ Value ’: [
20
0.1. SCHEDULE LIBRARY FOR WORKFLOW AUTOMATION 21
27 # Add metadata
28 workbook = writer . book
29 summary_sheet = writer . sheets [ ’ Summary ’]
30 summary_sheet [ ’ A1 ’] = f " Report Generated : { datetime . now () } "
• Font management: Use different fonts for headings and body text
• Headers and footers: Add consistent page numbering and document information
3 pdf = FPDF ()
4 pdf . add_page ()
5
6 # Title
7 pdf . set_font ( ’ Arial ’ , ’B ’ , 16)
8 pdf . cell (0 , 10 , " Daily Sales Report " , ln = True , align = ’C ’)
9 pdf . cell (0 , 10 , f " Date : { datetime . now () . strftime ( ’%Y -% m -% d ’) } " , ln = True , align = ’C ’)
10 pdf . ln (10)
11
12 # KPIs
13 pdf . set_font ( ’ Arial ’ , ’B ’ , 12)
14 pdf . cell (0 , 10 , " Key Performance Indicators " , ln = True )
21
22
• Include Metadata: Always add generation timestamp, data sources, and version information
• Log Generation Process: Track when reports are generated and any errors encountered
• Problem: Manual reporting on daily sales, inventory, and customer metrics takes 3 hours each morning
• Goal: Fully automate data collection, processing, analysis, and reporting with zero manual intervention
Pipeline Architecture
The pipeline follows a modern ELT (Extract, Load, Transform) pattern with multiple storage layers:
Pipeline Components
Component 1: Data Extraction (Scheduled) Automatically pull data from multiple source systems.
1 class DataExtractor :
2 def ext ra ct _s al es _d at a ( self ) :
3 df = pd . read_csv ( self . config [ ’ sales_source ’ ])
4 return df
5
6 def e x t r a c t _ i n v e n t o r y _ d a t a ( self ) :
7 response = requests . get ( self . config [ ’ inventory_api ’ ])
8 data = response . json ()
9 return pd . DataFrame ( data )
10
11 def e x t r a c t _ c u s t o m e r _ d a t a ( self ) :
12 return pd . read_csv ( self . config [ ’ customer_source ’ ])
Component 2: Data Lake Storage (Landing Zone) Store raw data in its original format for auditability and reprocessing.
1 class DataLake :
2 def store_raw_data ( self , data , source_name ) :
3 date_str = datetime . now () . strftime ( " %Y -% m -% d " )
4 partition_path = f " { self . base_path }/{ source_name }/ date ={ date_str } "
5 os . makedirs ( partition_path , exist_ok = True )
6 data . to_parquet ( f " { partition_path }/ raw_data . parquet " )
7 return f " { partition_path }/ raw_data . parquet "
Component 3: Data Transformation (Processing Layer) Clean, validate, and transform raw data into analytical
structures.
1 class DataTransformer :
2 def clean_sales_data ( self , raw_sales ) :
3 cleaned = raw_sales . drop_duplicates ( subset =[ ’ order_id ’ ])
4 cleaned [ ’ customer_id ’] = cleaned [ ’ customer_id ’ ]. fillna ( ’ UNKNOWN ’)
5 cleaned [ ’ amount ’] = cleaned [ ’ amount ’ ]. fillna (0) . astype ( float )
6 cleaned [ ’ order_date ’] = pd . to_datetime ( cleaned [ ’ order_date ’ ])
7 return cleaned
8
9 def calculate_kpis ( self , sales_df , inventory_df , customer_df ) :
10 kpis = {}
11 kpis [ ’ total_sales ’] = sales_df [ ’ amount ’ ]. sum ()
12 kpis [ ’ avg_order_value ’] = sales_df [ ’ amount ’ ]. mean ()
22
0.1. SCHEDULE LIBRARY FOR WORKFLOW AUTOMATION 23
1 class ReportGenerator :
2 def c r e a t e _ e x c e l _ d a s h b o a r d ( self , sales_df , kpis ) :
3 with pd . ExcelWriter ( f " reports / daily_dashboard . xlsx " ) as writer :
4 pd . DataFrame ( list ( kpis . items () ) , columns =[ ’ KPI ’ , ’ Value ’ ]) . to_excel ( writer , sheet_name = ’
Dashboard ’)
5 sales_df . to_excel ( writer , sheet_name = ’ Sales Details ’)
6
Component 5: Orchestration (Pipeline Controller) Coordinate extraction, storage, transformation, and report gener-
ation.
1 class An alytic sPipel ine :
2 def run ( self ) :
3 raw_sales = self . extractor . ex tra ct _s al es _d at a ()
4 raw_inventory = self . extractor . e x t r a c t _ i n v e n t o r y _ d a t a ()
5 raw_customers = self . extractor . e x t r a c t _ c u s t o m e r _ d a t a ()
6 self . data_lake . store_raw_data ( raw_sales , ’ sales ’)
7 cleaned_sales = self . transformer . clean_sales_data ( raw_sales )
8 kpis = self . transformer . calculate_kpis ( cleaned_sales , raw_inventory , raw_customers )
9 excel_report = self . reporter . c r e a t e _ e x c e l _ d a s h b o a r d ( cleaned_sales , kpis )
10 pdf_report = self . reporter . cr eate_p df_rep ort ( cleaned_sales , kpis )
23