📘 Functions & Methods¶

  • 📘 UDF
    • 🔹 Return
    • 🔹 Argument matching
    • 🔹 Function calling another
    • 🔹 Function defined inside function
    • 🔹 Argument - Default Values
    • 🔹 Lexical Scoping
  • 📘 Special Functions
    • 🔹 apply()
    • 🔹 map()
    • 🔹 applymap()
    • 🔹 Lambda function
  • 🏛️ OOPS
    • 🔹 Define Class
    • 🔹 Edit attributes of an instance
    • 🔹 Class attributes
    • 🔹 Update Class
    • 🔹 Update attribute in class
    • 🔹 Inheritence

In [35]:
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all"
In [36]:
import pandas as pd
import numpy as np

# IPython configuration for enhanced interactivity
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all"

# Set a seed for reproducibility
np.random.seed(42)

# Create a DataFrame with 5 rows and 5 columns of random integers from 1 to 20
df = pd.DataFrame(np.random.randint(1, 21, size=(5, 5)),
                  columns=['A', 'B', 'C', 'D', 'E'])
df
Out[36]:
A B C D E
0 7 20 15 11 8
1 7 19 11 11 4
2 8 3 2 12 6
3 2 1 12 12 17
4 10 16 15 15 19

📘 UDF¶

🔹 Return¶

In [37]:
# returns None when no value is specified
def my_fun(a,b):
    print(a)
    print(b)    
aa = my_fun(10, 20)
print(aa) # returns None
10
20
None
In [38]:
def my_fun(a,b):
    print(a)
    print(b)    
    return
my_fun(10, 20)
aa = my_fun(10, 20)
print(aa) # returns None
10
20
10
20
None
In [39]:
# function ends when return is encountered
def my_fun(a,b):
    print(a)
    return 10000
    print(b)    
my_fun(10, 20)
10
Out[39]:
10000
In [40]:
# return multiple values
def my_fun(a,b):
    print(a)
    print(b)    
    return 10000, 20000, 30000 # returns a tuple
my_fun(10, 20)
10
20
Out[40]:
(10000, 20000, 30000)
In [41]:
def my_fun(a,b):
    print(a)
    print(b)    
    return [10000, 20000, 30000]
my_fun(10, 20)
10
20
Out[41]:
[10000, 20000, 30000]

🔹 Argument matching¶

In [42]:
complex(3,5)
complex(real=3, imag=5)
complex(imag=3, real=5)

# complex(real = 3, 5) # error: positional argument follows keyword argument
complex(3, imag=5) # allowed
# complex(3, real=5) # error: 2 arguements passed for real parameter

# complex(imag = 3, 5) #error: positional argument follows keyword argument
# Rule: positional first, keyword next

# complex(real= 3, real= 5) # error, obviously
Out[42]:
(3+5j)
Out[42]:
(3+5j)
Out[42]:
(5+3j)
Out[42]:
(3+5j)

🔹 Function calling another¶

In [43]:
def fn_mumbai():
    print("Im in Mumbai")
    fn_bangalore()
    
def fn_bangalore():
    print("Im in Bangalore")
    fn_delhi()

def fn_delhi():
    print("Im in Delhi")
    
fn_mumbai()

del fn_mumbai, fn_bangalore, fn_delhi
Im in Mumbai
Im in Bangalore
Im in Delhi
  • Recursion
In [44]:
def factorial(n):
    if n==1 or n==0:
        return 1
    else:
        return n*factorial(n-1)
for i in range(5):
    print(i, factorial(i))
0 1
1 1
2 2
3 6
4 24
In [45]:
def fibonacci(n):    
    assert isinstance(n, int)
    assert n>=0    
    if n==0 or n==1:
        return 1
    else:
        return fibonacci(n-1) + fibonacci(n-2)

for i in range(5):
    print(i, fibonacci(i))
0 1
1 1
2 2
3 3
4 5

🔹 Function defined inside function¶

In [46]:
if False:
    def fn_mumbai():
        def fn_bangalore():
            print("Im in Bangalore")
        
        print("Im in Mumbai")
        fn_bangalore()

    fn_mumbai()
    fn_bangalore() # not available outside fn_mumbai()
In [47]:
# function can return another function
def multiplier(n):    
    def temp(x):
        return x*n    
    return temp

multiplier_2 = multiplier(2)
multiplier_2(10)
multiplier_3 = multiplier(3)
multiplier_3(10)
Out[47]:
20
Out[47]:
30

🔹 Argument - Default Values¶

In [48]:
def my_fun(x, y):
    print(x)
    
my_fun(10, 20) # works
if False:
    my_fun(10) # fails
10
In [49]:
def my_fun(x, y= 1000):
    print(x)
    print(y)
    
my_fun(10, 20) # works
my_fun(10) # works too, uses default
10
20
10
1000

🔹 Lexical Scoping¶

In [50]:
# block 1
def f1(a,b):
    print(dir())
    print(a)
    print(b)

a = 10
b = 20
[x for x in dir() if not x.startswith("_")]

print(a)
print(b)
f1(a=100, b=200)

print(a)
print(b)
Out[50]:
['In',
 'InteractiveShell',
 'Out',
 'a',
 'aa',
 'b',
 'df',
 'e1',
 'e2',
 'emp_1',
 'emp_2',
 'employee',
 'exit',
 'f1',
 'factorial',
 'fibonacci',
 'get_ipython',
 'i',
 'math',
 'multiplier',
 'multiplier_2',
 'multiplier_3',
 'my_fun',
 'np',
 'open',
 'pd',
 'quit',
 'x']
10
20
['a', 'b']
100
200
10
20
In [51]:
# block 2 - local variable, local values
def f1(a,b):
    print(dir())
    print(a)
    print(b)
    x = 999
    print(x)

x = 100
f1(a = 10, b=20)
print(x)
['a', 'b']
10
20
999
100
In [52]:
# block 3 - if a variable is absent in function, look one level above
def f1(a,b):
    print(dir())
    print(a)
    print(b)
    print(x)

x = 100
f1(a = 10, b=20)
print(x)
['a', 'b']
10
20
100
100

Back to the top


📘 Special Functions¶

🔹 apply()¶

  • Used to apply a function along the axis of the DataFrame (rows or columns).
  • Ideal for column or row operation
In [53]:
# function by column
df.apply(np.sum, axis=0) # axis = 0 is the default

# function by row
df.apply(np.sum, axis=1) # axis = 0 is the default
Out[53]:
A    34
B    59
C    55
D    61
E    54
dtype: int64
Out[53]:
0    61
1    52
2    31
3    44
4    75
dtype: int64

🔹 map()¶

  • Used with Series to substitute each value in a Series with another value.
  • Ideal for element operations
In [54]:
df
Out[54]:
A B C D E
0 7 20 15 11 8
1 7 19 11 11 4
2 8 3 2 12 6
3 2 1 12 12 17
4 10 16 15 15 19
In [55]:
# error, works only on series
# df.map(lambda x: x + 1000)

df['A'].map(lambda x: x + 1000)
# df[0].map(lambda x: x + 1000) # error
Out[55]:
0    1007
1    1007
2    1008
3    1002
4    1010
Name: A, dtype: int64

🔹 applymap()¶

  • Used to apply a function to each element of the DataFrame.
In [56]:
df.applymap(lambda x: x + 1000)
Out[56]:
A B C D E
0 1007 1020 1015 1011 1008
1 1007 1019 1011 1011 1004
2 1008 1003 1002 1012 1006
3 1002 1001 1012 1012 1017
4 1010 1016 1015 1015 1019
In [57]:
# df.apply(lambda row: row, axis = 0)
# df.apply(lambda row: row, axis = 1)

df.apply(lambda row: row['A'] + row['B'], axis = 1)
Out[57]:
0    27
1    26
2    11
3     3
4    26
dtype: int64

🔹 Lambda function¶

In [58]:
my_fun = lambda a : a + 10
my_fun(5)
Out[58]:
15

Map¶

In [59]:
list(map(lambda a : a + 10, [1,2,3,4,5]))

my_fun = lambda a : a + 10
list(map(my_fun , [1,2,3,4,5]))
Out[59]:
[11, 12, 13, 14, 15]
Out[59]:
[11, 12, 13, 14, 15]

Back to the top


🏛️ OOPS¶

🔹 Define Class¶

In [60]:
class employee:
    pass

emp_1 = employee()
emp_2 = employee()
    

print(emp_1)
emp_1.name = "ash red"
emp_1.email= "ashred@gmail.com"
print(emp_1)
print(emp_1.name)
print(emp_1.email)
<__main__.employee object at 0x14d650c90>
<__main__.employee object at 0x14d650c90>
ash red
ashred@gmail.com

🔹 Edit attributes of an instance¶

In [61]:
class employee:
    def __init__(self, name, pay): 
        self.name = name
        self.pay = pay        
        self.email = name + "@gmail.com"
        
    def print_name(self): # this is a method
        print(self.name)        
        
    def apply_new_year_bonus(self, bonus = 10):
        self.pay = self.pay + bonus
            
e1 = employee("abc def", 100) # e1 will be passed as self
e1
e1.name
e1.email # not brackets as email is an attribute of the class
e1.print_name() # need brackets since, print_name is a method
employee.print_name(e1) # works too. class_name.method_name(object_instance)
# e1.print_name_junk() # error since self if ommitted
Out[61]:
<__main__.employee at 0x14e2b81d0>
Out[61]:
'abc def'
Out[61]:
'abc def@gmail.com'
abc def
abc def
In [62]:
e1.pay
e1.apply_new_year_bonus(bonus = 2)
e1.pay

e1.pay
employee.apply_new_year_bonus(e1, bonus = 2)
e1.pay
Out[62]:
100
Out[62]:
102
Out[62]:
102
Out[62]:
104

🔹 Class attributes¶

In [ ]:
class employee:    
    new_year_bonus = 10
    
    def __init__(self, name, pay): 
        self.name = name
        self.pay = pay        
        self.email = name + "@gmail.com"
        
    def print_name(self): # this is a method
        print(self.name)        
        
    def apply_new_year_bonus(self):
        # self.pay = self.pay + new_year_bonus # error
        # self.pay = self.pay + employee.new_year_bonus # works
        self.pay = self.pay + self.new_year_bonus # works
                
e1 = employee("abd def", 100) # e1 will be passed as self
# getattr(e1) # fails
e1.__dict__
employee.__dict__

employee.new_year_bonus
e1.new_year_bonus

e1.pay
e1.apply_new_year_bonus() # bonus = 2
e1.pay

e1.whatever_attr = 999
e1.whatever_attr
e1.__dict__

e1.new_year_bonus = 999
e1.__dict__


e2 = employee("ash red", 766)
e2.whatever_attr
e2.__dict__
e2.new_year_bonus = 999
e1.__dict__

🔹 Update Class¶

In [ ]:
class employee:    
    employee_count = 0
    new_year_bonus = 10
    
    def __init__(self, name, pay): 
        self.name = name
        self.pay = pay        
        self.email = name + "@gmail.com"
        employee.employee_count = employee.employee_count + 1
        
    @classmethod
    def from_string(cls, data_string):
        name, pay = data_string.split("-")
        return cls(name, int(pay))
        
    def print_name(self): # this is a method
        print(self.name)        
        
    def apply_new_year_bonus(self):
        # self.pay = self.pay + new_year_bonus # error
        # self.pay = self.pay + employee.new_year_bonus # works
        self.pay = self.pay + self.new_year_bonus # works
    
    @classmethod
    def set_new_year_bonus(cls, amt):
        cls.new_year_bonus = amt
        # cls.new_year_bonus = amt + cls.new_year_bonus # works too

🔹 Update attribute in class¶

and all instances

In [ ]:
employee.employee_count # 0

e1 = employee("ash red", 100)
employee.employee_count # 1
e1.employee_count

e2 = employee("what ever", 200)
employee.employee_count # 2
e2.employee_count # e1 and e2 employee count updates to 2
In [ ]:
# classmethod - update new_year_bonus for class and all instances
e1 = employee("ash red", 100)
e1.new_year_bonus
e2 = employee("what ever", 200)
e2.new_year_bonus
employee.set_new_year_bonus(44)

employee.new_year_bonus
e1.new_year_bonus
e2.new_year_bonus
In [ ]:
# classmethod - generate instances dynamically 
e3 = employee.from_string("ashrith reddy-100")
e3.name
e3.pay
e3.__dict__
e3.new_year_bonus
e3.apply_new_year_bonus(); e3.pay

🔹 Inheritence¶

In [ ]:
class developer(employee): # Developer inherits from employee
    
    new_year_bonus = 200
    
    pass

print(help(developer))

dev_1 = developer("apple", 400)
dev_1.email
dev_1.pay

dev_1.apply_new_year_bonus()
dev_1.pay

Back to the top