Tuesday, March 19, 2024

Git steps

  1.  git Init ( Initialize Git repo)
  2. git status (shows untracked files)
  3. git add <filename> ( add files to git staging area) [ git add . to add all the files]
  4. git commit -m"<commit msg>" ( commit the change to git repo)
  5. git log (shows the commit)
                                git add                           git commit
Working Directory ----------> Staging Area ---------------> Local Git Repo

git diff <filename> shows the difference of content of the file.

git checkout <filename> rolls back the file to the last committed version.
git remote add <name> <url>
By convention, name is origin.

                           git push -u <remote name> <branch name>
Local Git Repo ---------------------------------------------------------> Remote Repo

git push -u origin main

To remove files from staging area,

git rm --cached - r .    <rm means remove, r means recursive>

Use .gitignore file to ignore some files.

git clone to clone from remote repo to local repo.

git clone <url>

For creating a new branch, 

git branch <branch-name>

To check available branches,

git branch

To switch branches,

git checkout <branch-name>

To merge changes

git merge <name-of-branch> ( go to main branch and then use merge )


Thursday, March 14, 2024

Apply any function of your choice

Pandas.apply allow the users to pass a function and apply it on every single value of the Pandas series.


import pandas as pd
data = {"K1": [12, 23], "K2": [25,50]}
df = pd.DataFrame(data, index=['R1', 'R2'])
print(df.apply(lambda x: x-2))

Output:
   K1  K2
R1  10  23
R2  21  48

Monday, March 11, 2024

Conditional Operation on DataFrame

 import pandas as pd

import numpy as np
from numpy.random import randn

np.random.seed(121)
# Create Dataframe
df = pd.DataFrame(randn(5, 4), ['A', 'B', 'C', 'D', 'E'], ['W', 'X', 'Y', 'Z'])
print("DataFrame:\n")
print(df)
print("Check if value > 0")
print(df[df>0])
print("Get all the row where Col. X value is >0")
print(df[df['X']>0])

O/p:
DataFrame:

          W         X         Y         Z
A -0.212033 -0.284929 -0.573898 -0.440310
B -0.330111  1.183695  1.615373  0.367062
C -0.014119  0.629642  1.709641 -1.326987
D  0.401873 -0.191427  1.403826 -1.968769
E -0.790415 -0.732722  0.087744 -0.500286
Check if value > 0
          W         X         Y         Z
A       NaN       NaN       NaN       NaN
B       NaN  1.183695  1.615373  0.367062
C       NaN  0.629642  1.709641       NaN
D  0.401873       NaN  1.403826       NaN
E       NaN       NaN  0.087744       NaN
Get all the row where Col. X value is >0
          W         X         Y         Z
B -0.330111  1.183695  1.615373  0.367062
C -0.014119  0.629642  1.709641 -1.326987

Pandas Series and Dataframe

What is Series?

In pandas, a series is a one-dimensional labeled array capable of holding any data type. It is a fundamental data structure in pandas and can be thought of as a column in a spreadsheet or a single column of a DataFrame. The Series is similar to a NumPy array but comes with additional features like labeled indices, which makes it more powerful and flexible.

import pandas as pd

# Creating a Series with custom indices data = [1, 2, 3, 4, 5] custom_indices = ['a', 'b', 'c', 'd', 'e'] series = pd.Series(data, index=custom_indices)

#1st argument is Data, next is index # Displaying the Series with custom indices print(series)

O/P:

a 1 b 2 c 3 d 4 e 5 dtype: int64


How to access value from Series?

print(series['c'])
O/p:
3

What is DataFrame?

In pandas, a DataFrame is a two-dimensional labeled data structure that is widely used for handling and manipulating tabular data. It can be thought of as a table, similar to a spreadsheet or a SQL table, where data is organized in rows and columns. 
import pandas as pd
import numpy as np
from numpy.random import randn

np.random.seed(121)
# Create Dataframe
df = pd.DataFrame(randn(5, 4), ['A', 'B', 'C', 'D', 'E'], ['W', 'X', 'Y', 'Z'])
print("DataFrame:\n")
print(df)
# Access Column: Return type -> Series
print("Access Column named 'W'\n")
print(df['W'])
# Access list of Column : Return type -> Dataframe
print("Access Columns named Y & Z\n")
print(df[['Y', 'Z']])
# Create a new column
df['V'] = df['Y'] + df['Z']
print("DataFrame:\n")
print(df)
# Drop the new column, Inplace -> To drop row, use axis=0
df.drop('V', axis=1, inplace=True)
print("DataFrame:\n")
print(df)
# Access Row using its label : Return type -> Series
print("Access Row labeled as 'B':\n")
print(df.loc['B'])
# Access Row using its internal index : Return type -> Series
print("Access Row 1:\n")
print(df.iloc[1])
#Access multiple rows : Return type-> DataFrame
print("Access Row labeled as B & C:\n")
print(df.loc[['B','C']])

O/p:
DataFrame:

          W         X         Y         Z
A -0.212033 -0.284929 -0.573898 -0.440310
B -0.330111  1.183695  1.615373  0.367062
C -0.014119  0.629642  1.709641 -1.326987
D  0.401873 -0.191427  1.403826 -1.968769
E -0.790415 -0.732722  0.087744 -0.500286
Access Column named 'W'

A   -0.212033
B   -0.330111
C   -0.014119
D    0.401873
E   -0.790415
Name: W, dtype: float64
Access Columns named Y & Z

          Y         Z
A -0.573898 -0.440310
B  1.615373  0.367062
C  1.709641 -1.326987
D  1.403826 -1.968769
E  0.087744 -0.500286
DataFrame:

          W         X         Y         Z         V
A -0.212033 -0.284929 -0.573898 -0.440310 -1.014208
B -0.330111  1.183695  1.615373  0.367062  1.982435
C -0.014119  0.629642  1.709641 -1.326987  0.382653
D  0.401873 -0.191427  1.403826 -1.968769 -0.564943
E -0.790415 -0.732722  0.087744 -0.500286 -0.412542
DataFrame:

          W         X         Y         Z
A -0.212033 -0.284929 -0.573898 -0.440310
B -0.330111  1.183695  1.615373  0.367062
C -0.014119  0.629642  1.709641 -1.326987
D  0.401873 -0.191427  1.403826 -1.968769
E -0.790415 -0.732722  0.087744 -0.500286
Access Row labeled as 'B':

W   -0.330111
X    1.183695
Y    1.615373
Z    0.367062
Name: B, dtype: float64
Access Row 1:

W   -0.330111
X    1.183695
Y    1.615373
Z    0.367062
Name: B, dtype: float64
Access Row labeled as B & C:

          W         X         Y         Z
B -0.330111  1.183695  1.615373  0.367062
C -0.014119  0.629642  1.709641 -1.326987

Similarly,

# Access a particular cell
print(df.loc['B', 'Y'])
# Access a subset of DF
print(df.loc[['B','C'],['X','Y']])

1.6153729283493805

          X         Y
B  1.183695  1.615373
C  0.629642  1.709641

Friday, March 8, 2024

Cool features of Numpy

a = np.array([6,0,9,0,0,5,0])
def nonzero(a):
return a!=0
print(a[nonzero(a)])

output:
[6 9 5]

 

Wednesday, March 6, 2024

Numpy Array vs Python List

 import numpy as np

list = [1,2,'Heaven','Hell']
arr = np.array(list)
#Please note, generally numpy generally does not contain Strings. It is mainly used
for numerical Operration.


list[2:] = 3
This is not allowed for normal python list. This will throw an error like
below:
    list[2:] = 3
TypeError: can only assign an iterable

However, it is allowed for numpy array and it will change the
original numpy array.

import numpy as np
list = [1,2,'Heaven','Hell']
arr = np.array(list)
arr[2:] = 3
print(arr)

Ouput: ['1' '2' '3' '3']

Now, no avoid changing the original array, we can use copy option as below.

Without copy:
import numpy as np
list = [1,2,'Heaven','Hell']
arr = np.array(list)
arr_copy =arr
arr_copy[2:] = 3
print(arr)
print(arr_copy)

O/p:
['1' '2' '3' '3']
['1' '2' '3' '3']

With copy:
import numpy as np
list = [1,2,'Heaven','Hell']
arr = np.array(list)
arr_copy =arr.copy()
arr_copy[2:] = 3
print(arr)
print(arr_copy)
O/p:
['1' '2' 'Heaven' 'Hell']
['1' '2' '3' '3']


list_one = [1,2,3]
list_two = [4,5,6]
print(f"Result of list addition: {list_one + list_two}")
arr_one = np.array(list_one)
arr_two = np.array(list_two)
print(f"Result of array addition: {arr_one + arr_two}")

O/p:
Result of list addition: [1, 2, 3, 4, 5, 6]
Result of array addition: [5 7 9]



Monday, March 4, 2024

Lamda Function

A lambda function is a small, anonymous function defined using the lambda keyword.

passwords = ['72tK3', '6zW5P3', '1P3=it3?', 'i"u7/auX{m']


allowed_passwords = list(filter(lambda x: len(x) >= 8, passwords))
print(allowed_passwords)

Filter Function

 Takes input a function which returns True/False and keeps the items which return True.

passwords = ['72tK3', '6zW5P3', '1P3=it3?', 'i"u7/auX{m']


def strong_password(password):
return len(password) >= 8


allowed_passwords = list(filter(strong_password, passwords))
print(allowed_passwords)

Output:
['1P3=it3?', 'i"u7/auX{m']

Sunday, March 3, 2024

Map function

 def names(firstname, lastname):

    """returns full name, inputs: first name and last name"""
return firstname + ' ' + lastname

fname = 'Priyanka'
lname = 'Chakraborti'
name = names(fname,lname)
print(name)
Output:
Priyanka Chakraborti

# I want to apply the same function over list

list_of_firstnames = ['Andy', 'Sam', 'Jim', 'Micheal']
list_of_surnames = ['Smith', 'Doe', 'Baker', 'Hendrix']

result = map(names,list_of_firstnames,list_of_surnames)
list_of_names = list(result)
print(list_of_names)
Output:
['Andy Smith', 'Sam Doe', 'Jim Baker', 'Micheal Hendrix']

Saturday, March 2, 2024

How to remove duplicates from list?

 Use set().


list_of_subjects= ['Physics', 'English', 'Statistics', 'English','Computer Science']
print(list(set(list_of_subjects)))

Output:
['Computer Science', 'English', 'Physics', 'Statistics']

Thursday, February 29, 2024

Slice Object in Python

Built-in type that represents a range of indices. It's commonly used with sequences (like lists, strings, and tuples) to extract portions of the sequence.


list_of_students = [(1,"Joe"), (2,"John"), (3,"Jacob"), (4,"Jack"),
 (5,"Karan"), (6, "Kavya"), (7,"Kamal")]

odd_slice = slice(0,len(list_of_students),2)
even_slice = slice(1,len(list_of_students),2)
list_of_group_one = list_of_students[odd_slice]
list_of_group_two = list_of_students[even_slice]
print(list_of_group_one)
print(list_of_group_two)

Output:

[(1, 'Joe'), (3, 'Jacob'), (5, 'Karan'), (7, 'Kamal')]
[(2, 'John'), (4, 'Jack'), (6, 'Kavya')]

Wednesday, February 28, 2024

Python Decorator

 In Python, a decorator is a design pattern that allows you to extend or modify the behavior of functions or methods without modifying their actual code. Decorators are applied using the @decorator syntax and are a concise way to wrap a function or method with additional functionality.

It helps you to off/on some functionality.


def log_function_call(func):
def wrapper(*args):
print(f"Calling {func.__name__} with arguments {args}")
result = func(*args)
print(f"{func.__name__} execution completed.")
return result
return wrapper

@log_function_call
def add_numbers(a, b):
return a + b

@log_function_call
def multiply_numbers(x, y):
return x * y

# Calling decorated functions
result_add = add_numbers(3, 5)
print(result_add)
result_multiply = multiply_numbers(
4, 6)
print(result_multiply)

Output:
Calling add_numbers with arguments (3, 5)
add_numbers execution completed.
8
Calling multiply_numbers with arguments (4, 6)
multiply_numbers execution completed.
24

Now if we comment out the decorator,
def log_function_call(func):
def wrapper(*args):
print(f"Calling {func.__name__} with arguments {args}")
result = func(*args)
print(f"{func.__name__} execution completed.")
return result
return wrapper

# @log_function_call
def add_numbers(a, b):
return a + b

# @log_function_call
def multiply_numbers(x, y):
return x * y

# Calling decorated functions
result_add = add_numbers(3, 5)
print(result_add)
result_multiply = multiply_numbers(
4, 6)
print(result_multiply)

Output:
8
24

Slice vs Split

Slicing is a broader concept applicable to sequences like lists, tuples, and strings. It involves extracting a portion, or a "slice," of a sequence based on indices. It is not an in-built function, it is a concept.

 art_board = "ArtBoard1"

list_of_shapes = ["Square", "Triangle", "Circle"]
tuple_of_colors = ('59D5E0', 'F5DD61', 'FAA300', 'F4538A')
# Slice works on both string and Iterables
# Get first 3 characters
print(art_board[:3])
# Reverse the list
print(list_of_shapes[::-1])
print(tuple_of_colors[1::2])


Output:
Art
['Circle', 'Triangle', 'Square']
('F5DD61', 'F4538A')

Split is used to divide a string into a list of substrings based
on a specified delimiter. 
By default, the delimiter is a space. 
# Split only works on String and create a list as outcome
my_name = "Priyanka Chakraborti"
my_first_name = my_name.split()[0]
my_last_name = my_name.split()[
1]
print(my_first_name, my_last_name, sep=',')
print(my_name.split())
# Change list to Tuple
print(tuple(my_name.split()))
Output:
Priyanka,Chakraborti
['Priyanka', 'Chakraborti']
('Priyanka', 'Chakraborti')

Understanding the Purpose of if name == 'main' in Python Scripts

 The __name__ == "__main__" construct in Python is a common pattern used to determine whether a Python script is being run as the main program or if it is being imported as a module into another script. This check is often used to control the execution of code that should only run when the script is executed directly.


We have two .py files.

File 1: sample.py


print('This is sample.py code')


def print_sample_text():
print('This is a sample text')


# __name__ is in-built variable
if __name__ == "__main__":
print('Sample.py is executed directly')
else:
print('Sample.py is imported')
print(f"Its name is: {__name__}")

File 2: demo.py
import sample
sample.print_sample_text()

if __name__ == "__main__":
print('demo.py is executed directly')
else:
print('demo.py is imported')
print(f"Its name is: {__name__}")

Now, if we run sample.py, output will be like:

This is sample.py code
Sample.py is executed directly

If we run demo.py, output will be like:
This is sample.py code
Sample.py is imported
Its name is: sample
This is a sample text
demo.py is executed directly

Now, notice the highlighted line. It should be executed 
only when we are running sample.py. But it is getting executed 
while import. Lets move this to the if block.

# print('This is sample.py code')


def print_sample_text():
print('This is a sample text')


# __name__ is in-built variable
if __name__ == "__main__":
print('Sample.py is executed directly')
print('This is sample.py code')
else:
print('Sample.py is imported')
print(f"Its name is: {__name__}")

Now, the output is:
Sample.py is imported
Its name is: sample
This is a sample text
demo.py is executed directly


Random module and String module

Example 1: 


import random,string


list_of_alphabets = [letter
for letter in string.ascii_letters]
random.shuffle(list_of_alphabets)
print(list_of_alphabets)
Output:
['Y', 'M', 'b', 'F', 'X', 'r', 'm', 'J', 'w', 'o', 
'Z', 'U', 'g', 'l', 'c', 'f', 'A', 'B', 'N', 'G', 'L', 'V', 'h', 
'n', 'I', 'T', 'x', 'y', 'E', 'H', 'D', 'q', 'd', 'O', 't', 'R', 
'S', 's', 'v', 'P', 'e', 'Q', 'k', 'p', 'i', 'W', 'K', 'C', 'j', 'z',
 'a', 'u']

Example 2:

import random
import string


def generate_random_password(length=12):
if length < 8 or length > 16:
raise ValueError("Password length must be between 8 and 16 characters")

password = []

# Combine letters and symbols
list_of_chars = [letter for letter in string.ascii_letters]
list_of_symbols = [symbol
for symbol in string.punctuation]
list_of_chars.extend(list_of_symbols)

# Shuffle the characters
random.shuffle(list_of_chars)

# Select characters for the password
for _ in range(length):
password.append(
random.choice(list_of_chars))

return ''.join(password)


# Generate a random password of length between 8 and 16
random_password = generate_random_password(random.randint(8, 16))
print(random_password)

Output:
x#=K@_&p.D

Access and modify global variable

 # Trying to change global variable

my_surname = 'Chakraborti'


def change_my_surname():
my_surname = 'Chakraborty'


print(f"My Surname before change: {my_surname}")
# Call change my surname function
change_my_surname()
print(f"My Surname after change: {my_surname}")

Output:
My Surname before change: Chakraborti
My Surname after change: Chakraborti

The above code could not change the my_surname (global variable). 
Actually in the change_my_surname() function,
it is creating a local variable.
This can be verified just writing a print statement.

# Access global variable
my_surname = 'Chakraborti'



def change_my_surname():
    my_surname = 'Chakraborty'
print(f"Changing surname to: {my_surname}")


print(f"My Surname before change: {my_surname}")
# Call change my surname function
change_my_surname()
print(f"My Surname after change: {my_surname}")
Output:
My Surname before change: Chakraborti
Changing surname to: Chakraborty
My Surname after change: Chakraborti

However, inside function, we can access the value.
# Access global variable
my_surname = 'Chakraborti'



def change_my_surname():
print(f"Current Surname: {my_surname}")


change_my_surname()

Output:
Current Surname: Chakraborti
Now to change the global variable,

# Change global variable
my_surname = 'Chakraborti'

def change_my_surname():
global my_surname
my_surname =
'Chakraborty'
print(f"Changing surname to: {my_surname}")


print(f"My Surname before change: {my_surname}")
# Call change my surname function
change_my_surname()
print(f"My Surname after change: {my_surname}")
Output:
My Surname before change: Chakraborti
Changing surname to: Chakraborty
My Surname after change: Chakraborty

Combine related information using zip function

Example 1:

 # Example with three lists

names = ["Alice", "Bob", "Charlie"]
ages = [25, 30, 22]
cities = ["New York", "San Francisco", "Los Angeles"]

# Using zip to combine the three lists
combined = zip(names, ages, cities)

# Converting the result to a list for printing
# Example with three lists
names = ["Alice", "Bob", "Charlie"]
ages = [25, 30, 22]
cities = ["New York", "San Francisco", "Los Angeles"]

# Using zip to combine the three lists
combined = zip(names, ages, cities)

# Converting the result to a list for printing
result_list = list(combined)

# Output
print(result_list)
Output:
[('Alice', 25, 'New York'), ('Bob', 30, 'San Francisco'), 
('Charlie', 22, 'Los Angeles')]

Modified Example 1:

# Example with three lists
names = ["Alice", "Bob", "Charlie"]
ages = [25, 30, 22]
cities = ["New York", "San Francisco", "Los Angeles"]

# Using zip to combine the three lists
combined = zip(names, ages, cities)

# Converting the result to a list for printing
# Example with three lists
names = ["Alice", "Bob", "Charlie"]
ages = [25, 30, 22]
cities = ["New York", "San Francisco", "Los Angeles"]

# Using zip to combine the three lists
combined = zip(names, ages, cities)

for emp in combined:
print(f"Employee name: {emp[0]},
Employee Age: {emp[1]} and Work location is {emp[2]}")

Output:
Employee name: Alice, Employee Age: 25 and Work location is New York
Employee name: Bob, Employee Age: 30 and Work location is San Francisco
Employee name: Charlie, Employee Age: 22 and Work location is Los Angeles

**kwargs to handle any number of keyword arguments

 def my_grocery_list(**kwargs):

    print(kwargs)
print(type(kwargs))
for item in kwargs:
print(f"{kwargs[item]} {item}")

my_grocery_list(toothpaste='Colgate',toothbrush='Colgate',
num_of_toothbrush=2)

Output:

{'toothpaste': 'Colgate', 'toothbrush': 'Colgate',
'num_of_toothbrush': 2}
<class 'dict'> Colgate toothpaste Colgate toothbrush 2 num_of_toothbrush