Module 6 — Python, Numpy, Pandas, Visualization
Why Python?
- Python is a widely used, general-purpose programming language.
- Easy to start working with.
- Scientific computation functionality similar to Matlab and Octave.
- Used by major deep learning frameworks such as PyTorch and TensorFlow.
Python Installation
Anaconda
- Anaconda is a popular Python environment/package manager
- Install from (here)
- We suggest using Python >= 3.8
IDEs:
- IDLE
- IDLE comes with Python installation by default.
- Jupyter Notebook and JupyterLab (link)
- Jupyter Notebook and JupyterLab are two web-based notebooks of the bigger project called Project Jupyter Documentation. A notebook is a shareable document that combines computer code, plain language descriptions, data, rich visualizations like 3D models, charts, graphs and figures, and interactive controls.
- Once you installed Anaconda, you can install and launch Jupyter Notebook or JupyterLab, and start immediately writing and running Python codes.
- PyCharm
- PyCharm is a widely used IDE (link).
- Spyder
- Spyder already exists in Anaconda once it is installed. Spider is very similar to Matlab (link).
Printing a message
print('Hello World')
Hello World
Arithmetics
x = 10
y = 3
print(x + y)
print(x - y)
print(x ** y)
print(x / y) # in python 2 returns 3
print(x / float(y))
print(x // y) # returning the quotient (integer division)
print(str(x) + '' + '' + str(y))
13
7
1000
3.3333333333333335
3.3333333333333335
3
103
Logical
print(True and False) # & same as and
print(True or False) # | same as or
print(not (True or False))
False
True
False
Relational
print(3 == 3)
print(1 == 5)
print(12 != 30)
print(-2.5 < 3)
print(1 <= 40)
print(0 > 10)
print(0 >= 10)
True
False
True
True
True
False
False
Bitwise
print(format(10, '04b'))
print(format(7, '04b'))
print("============ Bitwise AND operation ===========")
x = 10 & 7
print(x, format(x, '04b'))
print("============ Bitwise OR operation ============")
x = 10 | 7
print(x, format(x, '04b'))
print("============ Bitwise XOR operation ===========")
x = 10 ^ 7
print(x, format(x, '04b'))
print("============ Bitwise Left Shift ===============")
x = x << 1
print(x, format(x, '04b'))
print("============ Bitwise Right Shift ==============")
x = x >> 2
print(x, format(x, '04b'))
1010
0111
============ Bitwise AND operation ===========
2 0010
============ Bitwise OR operation ============
15 1111
============ Bitwise XOR operation ===========
13 1101
============ Bitwise Left Shift ===============
26 11010
============ Bitwise Right Shift ==============
6 0110
Membership
- The
range()
function returns a sequence of numbers, starting from 0 by default, and increments by 1 (by default), and stops before a specified number.- range(start, stop, step)
print('hell' in 'hello')
print(3 in range(5), 7 in range(5))
True
True False
Identity
x = [2,3]
y = [2,3]
print(x == y, x is y)
print(id(x), id(y))
x = 'hello'
y = 'hello'
print(x == y, x is y)
print(id(x), id(y))
True False
140619199022464 140619199022016
True True
140619189642544 140619189642544
Assignment
x = 10
print(x)
x = x + 2
print(x)
x *= 2
print(x)
x += 5
print(x)
x -= 3
print(x)
10
12
24
29
26
Built-in Values
True # The boolean value True.
False # The boolean value False.
None # The singleton value None.
float("inf") # The floating-point value inf.
float("-inf") # The floating-point value -inf.
float("nan") # The floating-point value nan.
complex('1+2j') # Return a complex number with the value real + imag*1j or convert a string or number to a complex number
List of keywords in Python
There cannot be used as a keyword as a variable name, function name, or any other identifier.
False await else import pass
None break except in raise
True class finally is return
and for lambda try continue
as def from while nonlocal
assert del global not with
async elif if or yield
Special variables
- These variables are all reserved by Python and should not be used for other purposes.
__author__
The name of the author of the module__doc__
A string that contains the documentation for the module__file__
The path to the module file__name__
The name of the module__package__
The name of the package that the module is part of__main__
The namespace that a Python module is running in__doc__
Printing out the docstring that appears in a class or method__class__
Returning the class of an instance__dict__
Returning, as a dictionary, all attributes of a class instance:dir()
Rreturning, as a list, every associated method or attribute
String Format
There are 3 ways to format strings.
- Old method using “%” sign
- Using format() method
- Using f-String
Old method using “%” sign
- Using %n1.n2f
- n1 is the total minimum number of digits the string should contain
- Filling with whitespace if the entire number does not have this many digits
- n2 placeholder denotes the number of decimal point
- Two methods %s and %r actually convert any Python object to a string
- n1 is the total minimum number of digits the string should contain
s = 'DEAR'
print("Place another string with a mod and s: %s" %(s))
print("Floating point numbers: %1.2f" %(13.144567))
print("Floating point numbers: %2.4f" %(13.144567))
print("Here is a number: %s. Here is a string: %s" %(123.1,'hi'))
print("Here is a number: %r. Here is a string: %r" %(123.1,'hi'))
Place another string with a mod and s: DEAR
Floating point numbers: 13.14
Floating point numbers: 13.1446
Here is a number: 123.1. Here is a string: hi
Here is a number: 123.1. Here is a string: 'hi'
Using format() method
- This approach is more cleaner to write
print("This is a string with a {var}".format(var='DEAR'))
print("One: {var1}, Two: {var1}, Three: {var1}".format(var1='HI !!!')) # Multiple times
print("Object 1: {var1}, Object 2: {var2}, Object 3: {var1}".format(var1=1,var2='two',var3=12.3)) # Several Objects
This is a string with a DEAR
One: HI !!!, Two: HI !!!, Three: HI !!!
Object 1: 1, Object 2: two, Object 3: 1
Using f-String
- f-string is the modern way, and the shortest and best approach to format a string:
- Syntax \(~\Longrightarrow\)
python f"This is an f-string {var_name} and {var_name}."
language = "Python"
school = "freeCodeCamp"
print(f"I'm learning {language} from {school}.")
num1 = 83.98765
num2 = 9.876543218765
print(f"The product of {num1} and {num2} is {num1 * num2}.")
print(f"The product of {num1:.2f} and {num2:.4f} is {num1 * num2:.2f}.")
I'm learning Python from freeCodeCamp.
The product of 83.98765 and 9.876543218765 is 829.5076550675084.
The product of 83.99 and 9.8765 is 829.51.
String methods
- find()
- Returns the index of first occurrence of substring
- islower()
- Checks if all Alphabets in a String are Lowercase
- isnumeric()
- Checks Numeric Characters
- replace()
- Replaces Substring Inside
- lstrip()
- Removes Leading Characters
- rstrip()
- Removes Trailing Characters
- split()
- Splits String from Left
- join()
- Returns a Concatenated String
string = " !Hi man how are you?&"
print(string.find("man"), "\n")
print(string.islower(), "\n")
print(string.isnumeric(), "\n")
print(string.replace("man", "Sir"), "\n")
print(string.lstrip(" !"), "\n")
print(string.rstrip("&"), "\n")
print(string.split(" "), "\n")
a = string.split(" ")
" ".join(a)
5
False
False
!Hi Sir how are you?&
Hi man how are you?&
!Hi man how are you?
['', '!Hi', 'man', 'how', 'are', 'you?&']
' !Hi man how are you?&'
Code blocks are created using indents.
Indents can be 2 or 4 spaces but should be consistent throughout the file.
def fib(n):
# Indent level 1: function body
if n <= 1:
# Indent level 2: if statement body
return 1
else:
# Indent level 2: else statement body
return fib(n-1)+fib(n-2)
Zip/Unzip
- Zip allows to combine two collections.
- it returns back an iterator.
- We use “*” for unzipping.
first = ['Joe','Earnst','Thomas','Martin','Charles']
last = ['Schmoe','Ehlmann','Fischer','Walter','Rogan','Green']
age = [23, 65, 11, 36, 83]
for first_name, last_name, age in zip(first_name, last_name, age):
print(f"{first_name} {last_name} is {age} years old")
Joe Schmoe is 23 years old
Earnst Ehlmann is 65 years old
Thomas Fischer is 11 years old
Martin Walter is 36 years old
Charles Rogan is 83 years old
full = [('Joe', 'Schmoe', 23),
('Earnst', 'Ehlmann', 65),
('Thomas', 'Fischer', 11),
('Martin', 'Walter', 36),
('Charles', 'Rogan', 83)]
first_name, last_name, age = list(zip(*full))
print(f"first name: {first_name}")
print(f"last name: {last_name}")
print(f"age: {age}")
print()
print('a' in dict(zip('abc', range(3))))
first name: ('Joe', 'Earnst', 'Thomas', 'Martin', 'Charles')
last name: ('Schmoe', 'Ehlmann', 'Fischer', 'Walter', 'Rogan')
age: (23, 65, 11, 36, 83)
True
Loops
For loops (If you want an index \(\Longrightarrow\) using enumerate()!)
for i, name in enumerate(['Zack','Jay','Richard']):
print('Hi ' + '! {0}: {1:.4f}'.format(name, i))
While Loops
while True:
print('We are stuck in a loop...')
break # Break out of the while loop
We are stuck in a loop...
What about for (i=0; i<10; i++)? \(\Longrightarrow\) using range():
for i in range(5):
print('Line' + str(i))
Line0
Line1
Line2
Line3
Line4
Looping over a list, unpacking tuples:
for x, y in [(1,10), (2,20), (3,30)]:
print(x, y)
1 10
2 20
3 30
- Enumerate is a built-in function of Python, allowing us to loop over something and have an automatic counter.
for i, item in enumerate(["Ali", "John", "Zach"]):
print(i, item)
0 Ali
1 John
2 Zach
Conditions
- Similar to other languages, for writing conditions we can use if, elif, else, or in while argumnet as shown below:
if 5 + 1 == 6:
print("yeah!")
'correct' if 1 + 1 == 3 else 'incorrect'
if 1+1 == 3:
print("oops")
else:
print("yeah!")
for grade in [94, 79, 81, 57]:
if grade > 90:
print('A')
elif grade > 80:
print('B')
elif grade > 70:
print('C')
else:
print('Are you in the right class?')
i = 4
while i > 0:
print(i)
i -= 1
print("=======================")
for i in range(1, 4):
if i % 2 == 0:
continue
print(i)
print("=======================")
for i in range(1, 4):
if i % 2 == 0:
break
print(i)
print("=======================")
for i in range(1, 4):
if i % 2 == 0:
pass
else:
print(i)
yeah!
yeah!
A
C
B
Are you in the right class?
4
3
2
1
=======================
1
3
=======================
1
=======================
1
3
Errors and Error Handling
- We can handle errors nicely using try and exception built-in:
try:
1 / 0
except ZeroDivisionError as e:
print(e)
division by zero
assert()
- The assert keyword lets one test if a condition in yotheur code returns True, if not, the program will raise an AssertionError.
x = "Good"
#if condition is False, AssertionError is raised:
assert x == "OK", "x should be 'OK'"
---------------------------------------------------------------------------
AssertionError Traceback (most recent call last)
/var/folders/nf/r9pb0kd107s0x03h3r5bgl6r0000gn/T/ipykernel_29083/2461160715.py in <module>
2
3 #if condition is False, AssertionError is raised:
----> 4 assert x == "OK", "x should be 'OK'"
AssertionError: x should be 'OK'
List of possible exceptions (link):
AssertionError Raised when an assert statement fails.
AttributeError Raised when attribute assignment or reference fails.
EOFError Raised when the input() function hits the end-of-file condition.
FloatingPointError Raised when a floating point operation fails.
GeneratorExit Raise when close() method a generator is called.
ImportError Raised when the imported module is not found.
IndexError Raised when the index of a sequence is out of range.
KeyError Raised when a key is not found in a dictionary.
KeyboardInterrupt Raised when the user hits the interrupt key (Ctrl+C or Delete).
MemoryError Raised when an operation runs out of memory.
NameError Raised when a variable is not found in local or global scope.
NotImplementedError Raised by abstract methods.
OSError Raised when system operation causes system-related error.
OverflowError Raised when the result of an arithmetic operation is too large to be represented.
ReferenceError Raised when a weak reference proxy is used to access a garbage collected referent.
RuntimeError Raised when an error does not fall under any other category.
StopIteration Raised by next() function to indicate that there is no further item to be returned by iterator.
SyntaxError Raised by the parser when a syntax error is encountered.
IndentationError Raised when there is incorrect indentation.
TabError Raised when indentation consists of inconsistent tabs and spaces.
SystemError Raised when the interpreter detects an internal error.
SystemExit Raised by sys.exit() function.
TypeError Raised when a function or operation is applied to an object of incorrect type.
UnboundLocalError Raised when a reference is made to a local variable in a function or method, but no value has been bound to that variable.
UnicodeError Raised when a Unicode-related encoding or decoding error occurs.
UnicodeEncodeError Raised when a Unicode-related error occurs during encoding.
UnicodeDecodeError Raised when a Unicode-related error occurs during decoding.
UnicodeTranslateError Raised when a Unicode-related error occurs during translating.
ValueError Raised when a function gets an argument of the correct type but improper value.
ZeroDivisionError Raised when the second operand of division or modulo operation is zero.
Python data structures
- Sequence containers - list, tuple
- Mapping containers - set, dict
- The collections module (link)
- deque (list-like container with fast appends and pops on either end)
- Counter (dict subclass for counting hashable objects)
- OrderedDict (dict subclass that remembers the order entries were added)
- defaultdict (dict subclass that calls a factory function to supply missing values)
List
- List is a collection that is ordered and changeable (Mutable). Allows duplicate members.
empty_list = [] # Empty list
empty_list = list() # Empty list
names = ['Zach','Jay']
print(names[0] == 'Zach')
names.append('Richard')
print(len(names) == 3)
print(names)
names.extend(['Abi','Kevin'])
print(names)
names = [] # Creates an empty list
names = list() # Also creates an empty list
stuff = [1, ['hi','bye'], -0.12, None] # Can mix types
print(stuff)
True
True
['Zach', 'Jay', 'Richard']
['Zach', 'Jay', 'Richard', 'Abi', 'Kevin']
[1, ['hi', 'bye'], -0.12, None]
List slicing (indexing)
- x[start:stop:step]
- start - starting integer where the slicing of the object starts
- stop - integer until which the slicing takes place. The slicing stops at index stop - 1.
- step - integer value which determines the increment between each index for slicing
numbers = [0, 1, 2, 3, 4, 5, 6]
print(numbers[0:3]) # numbers[:3]
print(numbers[5:]) # numbers[5:7]
print(numbers[:]) # numbers
print(numbers[-1]) # Negative index wraps around
print(numbers[-3:])
print(numbers[3:-2]) # Can mix and match
print(numbers[0:5:2]) # numbers[:5:2]
numbers[::-1]
[0, 1, 2]
[5, 6]
[0, 1, 2, 3, 4, 5, 6]
6
[4, 5, 6]
[3, 4]
[0, 2, 4]
[6, 5, 4, 3, 2, 1, 0]
List Methods:
- sort(): Sorts the list in ascending order.
- We can also use sorted() built-in function.
- append(): Adds a single element to a list.
- extend(): Adds multiple elements to a list.
- index(): Returns the first appearance of the specified value.
- max(): It returns an item from the list with max value.
- min(): It returns an item from the list with min value.
- len(): It gives the total length of the list.
- pop(): It removes and returns the item at index (default last).
- remove(): It removes the first occurrence of a value
a = [5, 1, 7, -1, 4, 10, 3]
print("======= using sorted function =======")
print(sorted(a))
print("======== using sort() method ========")
a.sort()
print(a)
a.append(100)
print(a)
print("======== extend method ========")
b = [20, 20, 40]
a.extend(b)
print(a)
a = [5, 1, 7, -1, 4, 10, 3]
print(a.index(-1))
print(max(a))
print(min(a))
print(len(a))
print("======== pop method ===========")
print(a.pop(5))
print(a)
print("======== remove method ========")
a.remove(4)
print(a)
======= using sorted function =======
[-1, 1, 3, 4, 5, 7, 10]
======== using sort() method ========
[-1, 1, 3, 4, 5, 7, 10]
[-1, 1, 3, 4, 5, 7, 10, 100]
======== extend method ========
[-1, 1, 3, 4, 5, 7, 10, 100, 20, 20, 40]
3
10
-1
7
======== pop method ===========
10
[5, 1, 7, -1, 4, 3]
======== remove method ========
[5, 1, 7, -1, 3]
Tuple
- A tuple is a collection that is ordered and unchangeable (Immutable).
- Tuples cannot be modified. This makes a good choice for key in hashtables or dictionaries.
empty_tuple = () # Empty tuple
empty_tuple = tuple() # Empty tuple
names = ('Zach', 'Jay') # Note the parentheses
print(names[0] == 'Zach')
print(len(names) == 2)
print(names)
try:
names[0] = 'Richard'
except TypeError as e:
print(e)
empty = tuple() # Empty tuple
single = (10,) # Single-element tuple. Comma matters!
print(single)
True
True
('Zach', 'Jay')
'tuple' object does not support item assignment
(10,)
Tuple Methods
- count(): Returns the number of times a specified value occurs in a tuple
- index(): Searches the tuple for a specified value and returns the position of where it was found
a = (2, 3, 10, -1, 10, 10)
print(a.count(10))
print(a.index(-1))
print(a.index(10))
3
3
2
Set
- A set is a collection that is unordered and unindexed. In Python, sets are written with curly brackets.
- Please note that if you create a set with a curly bracket, it should be non-empty; otherwise, it is treated as a dictionary.
- It is suitable for creating a unique collection of objects.
- We can do set mathematical operations with this data structure such as unioin, difference, intersection, etc.
empty_set = set() # Empty set
names = {'Zach', 'Jay', 'Zach'} # Note the curly brackets and duplicates
print(len(names) == 2)
for name in names:
print(name)
try:
print(names[0])
except TypeError as e:
print(e)
try:
names[0] = 'Richard'
except TypeError as e:
print(e)
names_2 = {'Jay', 'Richard'}
print("========= set difference ============")
print(names - names_2)
print(names.difference(names_2))
print("========= set union ============")
print(names | names_2)
print(names.union(names_2))
print("========= set intersection ============")
print(names & names_2)
print(names.intersection(names_2))
True
Zach
Jay
'set' object is not subscriptable
'set' object does not support item assignment
========= set difference ============
{'Zach'}
{'Zach'}
========= set union ============
{'Zach', 'Richard', 'Jay'}
{'Zach', 'Richard', 'Jay'}
========= set intersection ============
{'Jay'}
{'Jay'}
Set Methods
- add(): Adds an element to the set
- clear(): Removes all the elements from the set
- copy(): Returns a copy of the set
- difference(): Returns a set containing the difference between two or more sets
- difference_update(): Removes the items in this set that are also included in another, specified set
- discard(): Remove the specified item
- intersection(): Returns a set, that is the intersection of two or more sets
- isdisjoint(): Returns whether two sets have an intersection or not
- issubset(): Returns whether another set contains this set or not
- issuperset(): Returns whether this set contains another set or not
- pop(): Removes an element from the set
- remove(): Removes the specified element
- symmetric_difference(): Returns a set with the symmetric differences of two sets
- union(): Return a set containing the union of sets
- update(): Update the set with another set, or any other iterable
a = {"Ali", 4, "John", 6, 8, -1}
print("========= add method ============")
a.add("Jeff")
print(a)
print("========= clear method ============")
a.clear()
print(a)
a = {"Ali", 4, "John", 6, 8, -1}
b = {6, "Ali", 100, 3.6}
print("======= difference method ========")
print(a.difference(b))
print("========= discard method ==========")
a.discard("John")
print(a)
print("======= intersection method ========")
print(a.intersection(b))
print(a.isdisjoint(b))
c = {4, 8}
print("========= issubset method =========")
print(c.issubset(a))
print("======= issuperset method ========")
print(a.issuperset(b))
print("========= remove method ==========")
b.remove(3.6)
print(b)
print("======= symmetric_difference method ========")
print(a.symmetric_difference(b))
print(a.union(b))
a = {"Ali", 4, "John", 6, 8, -1}
print("========= update method ==========")
a.update(b)
print(a)
========= add method ============
{'John', 'Jeff', 4, 6, 8, 'Ali', -1}
========= clear method ============
set()
======= difference method ========
{8, 'John', 4, -1}
========= discard method ==========
{4, 6, 8, 'Ali', -1}
======= intersection method ========
{'Ali', 6}
False
========= issubset method =========
True
======= issuperset method ========
False
========= remove method ==========
{100, 'Ali', 6}
======= symmetric_difference method ========
{4, 100, 8, -1}
{4, 100, 6, 8, 'Ali', -1}
========= update method ==========
{'John', 4, 100, 6, 8, 'Ali', -1}
Dictionary (key-value pairs)
- A dictionary is a collection that is unordered, changeable, and indexed.
- In Python, dictionaries are written with curly brackets, and they have keys and values (This is used for creating hash tables).
phonebook = dict() # Empty dictionary
phonebook = {'Zach': '12-37'} # Dictionary with one item
phonebook['Jay'] = '34-23' # Add another item
print(phonebook)
print('Zach' in phonebook)
print('Kevin' in phonebook)
print(phonebook['Jay'])
print("=================================")
for name, number in phonebook.items():
print(name, number)
print("=================================")
del phonebook['Zach'] # Delete an item
print(phonebook)
print("=================================")
for name, number in phonebook.items():
print(name, number)
{'Zach': '12-37', 'Jay': '34-23'}
True
False
34-23
=================================
Zach 12-37
Jay 34-23
=================================
{'Jay': '34-23'}
=================================
Jay 34-23
Dictionary Methods
- get(): Returns the value of the specified key
- items(): Returns a list containing a tuple for each key-value pair
- keys(): Returns a list containing the dictionary’s keys
- values(): Returns a list of all the values in the dictionary
- update(): Updates the dictionary with the specified key-value pairs
- clear(): Removes all the elements from the dictionary
a = {"Ali": 33, "John": 25, "Jeff": 58}
print(a.get("Ali"))
print(a.items())
print(a.keys())
print(a.values())
b = {"Chris": 14}
a.update(b)
print(a)
a.clear()
print(a)
33
dict_items([('Ali', 33), ('John', 25), ('Jeff', 58)])
dict_keys(['Ali', 'John', 'Jeff'])
dict_values([33, 25, 58])
{'Ali': 33, 'John': 25, 'Jeff': 58, 'Chris': 14}
{}
Functions
The syntax for defining functions is as follows:
def f(a, b):
"""Doing something with inputs a and b.
"""
return someting
def Myfunction():
print('Hello World!')
Myfunction() # call the function
print('Outside function')
# function with two arguments
def add_numbers(num1, num2):
sum = num1 + num2
print('Sum: ',sum)
# Return value in a function with two arguments
def add_numbers(num1, num2):
sum = num1 + num2
return sum
result = add_numbers(num1, num2) # call the function
print(result)
def test_function(a. b): # some placeholder (doinf nothing) for a function
pass
*args and **kwargs
- These two are mostly used in function definitions.
- *args (non-keyworded) and **kwargs (keyworded) arguments allow one to pass an unspecified number of arguments to a function.
- it is not required to write *args or **kwargs. Only the * (asterisk) is necessary.
- Order of different types of arguments in a function
- formal args — > *args — > **kwargs
def test(normal_arg, *argv):
print("first normal arg:", normal_arg)
for arg in argv:
print("another arg through *argv:", arg)
test(25, 'John', 'Zach', 'Marry')
first normal arg: 25
another arg through *argv: John
another arg through *argv: Zach
another arg through *argv: Marry
def test(**kwargs):
for key, value in kwargs.items():
print(f"{key}: {value}")
test(name="John", age=23)
name: John
age: 23
Using *args and **kwargs to call a function
def test(arg1, arg2, arg3):
print("arg1:", arg1)
print("arg2:", arg2)
print("arg3:", arg3)
print("========== with *args ==========")
args = ("five", 4, 1)
test(*args)
print("========= with **kwargs ========")
kwargs = {"arg3": 1, "arg2": "two", "arg1": 15}
test(**kwargs)
========== with *args ==========
arg1: five
arg2: 4
arg3: 1
========= with **kwargs ========
arg1: 15
arg2: two
arg3: 1
Lambda Function
- A lambda function is a small anonymous function.
- A lambda function can take any number of arguments, but can only have one expression.
lambda argument: manipulate(argument)
# These two operations are identical
def add(a, b):
return a+b
add_lambda = lambda x, y: x + y
print("======== regular function =========")
print(add(3, 5))
print("====== using lambda function =======")
print(add_lambda(3, 5))
print("====================================")
add = lambda a, b, c : a + b + c
print(add(5, 6, 2))
# Sorting a List of tuples
a = [(1, 2), (4, 1), (9, 10), (13, -3)]
a.sort(key=lambda x: x[1])
print(a)
======== regular function =========
8
====== using lambda function =======
8
====================================
13
[(13, -3), (4, 1), (1, 2), (9, 10)]
Some built-in functions:
- map(): It applies a function to all the items in an input_list
python map(function_to_apply, list_of_inputs)
- filter: It creates a list of elements for which a function returns true
- reduce: It applies a rolling computation to sequential pairs of values in a list
- All these functions return an iterator that yields the output of the desired function. To get the whole results, you should wrap it with the list() method.
map()
# passing all the list elements to a function one-by-one and then collecting the output. For instance:
items = [1, 2, 3, 4, 5]
squared = []
for i in items:
squared.append(i**2)
# map allows us to implement this in a much simpler and nicer way. Here you go:
items = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x**2, items))
print(squared)
[1, 4, 9, 16, 25]
filter()
number_list = range(-5, 5)
less_than_zero = list(filter(lambda x: x < 0, number_list))
print(less_than_zero)
[-5, -4, -3, -2, -1]
reduce()
# multiplication without reduce
product = 1
list = [1, 2, 3, 4]
for num in list:
product = product * num
# multiplication with reduce
from functools import reduce
product = reduce((lambda x, y: x * y), [1, 2, 3, 4])
print(product)
24
I/O file operation
- Open a file
- Read or write (perform operation)
- Close the file
- “r” Open a file for reading (default)
- “w” Open a file for writing. Creates a new file if it does not exist or overwrite the file if it exists.
- “a” Open a file for appending at the end of the file without overwriting it. Creates a new file if it does not exist.
- “b” Open in binary mode
# Wrting in a file
with open("test.txt", "w") as file:
file.write("Hello My Dear !!!")
# Reading a file
with open("test.txt", "r") as file:
a = file.read()
print(a)
Hello My Dear !!!
- Pickle file
- Python’s Pickle module is a format used to serialize and deserialize data types.
- ickle objects cannot be loaded using any other programming language.
- Pickle can serialize almost every used built-in Python data type:
- list, dictionary, numpy array, pandas dataframe, machine learning. models
- serialization formats like JSON does not support tuples and datetime objects.
- Pickle also retains the exact state of the object, while JSON does not do it.
- Pickle is slower and produces larger serialized values than JSON.
- Pickle is unsafe because it can execute malicious Python callables to construct objects.
import pickle
names = ["Abby", "John", "Maggy"]
print("==== before loading pickle file ====")
print(names)
with open('names.pkl', 'wb') as f: # open a text file
pickle.dump(names, f) # serialize the list
with open('names.pkl', 'rb') as f: # open a text file
array = pickle.load(f) # serialize the list
print("==== after loading pickle file ====")
print(array)
==== before loading pickle file ====
['Abby', 'John', 'Maggy']
==== after loading pickle file ====
['Abby', 'John', 'Maggy']
- dumps() and loads() functions are used to serialize and and deserialize byte objects.
Classes (Object-oriented Programming)
- The super() built-in returns a proxy object, a substitute object that can call methods of the base class via delegation. Indirection call or ability to reference base object with super().
class Animal(object):
def __init__(self, species, age): # Constructor `a = Animal(‘bird’, 10)`
self.species = species # Refer to instance with `self`
self.age = age # All instance variables are public
def isPerson(self): # Invoked with `a.isPerson()`
return self.species == 'Homo Sapiens'
def growup(self):
self.age += 1
class Dog(Animal): # Inherits Animal’s methods
def __init__(self, age):
super().__init__(self.__class__.__name__, age)
def growup(self): # Override for dog years
self.age += 7
mydog = Dog(5)
print(mydog.species, mydog.age)
print(mydog.isPerson())
mydog.growup()
print(mydog.age)
Dog 5
False
12
List, set and dictionary comprehensions
print("===== list comprehension =====")
print([a**2 for a in range(6)])
print("===== list comprehension with condition =====")
print([a**2 for a in range(6) if a%2 != 0])
persons = [("John", 23), ("Abby", 18), ("Frank", 39)]
print("===== set comprehension =====")
print({item[0] for item in persons})
print("===== dictionary comprehension =====")
print({name: age for name, age in persons})
===== list comprehension =====
[0, 1, 4, 9, 16, 25]
===== list comprehension with condition =====
[1, 9, 25]
===== set comprehension =====
{'Frank', 'Abby', 'John'}
===== dictionary comprehension =====
{'John': 23, 'Abby': 18, 'Frank': 39}
Iteration, Iterators, Iterables, and Generators
Iteration
- It is the process of taking an item from something e.g a list. When we use a loop to loop over something which is called iteration.
Iterable
- An iterable is any object in Python which has an _iter_() or a _getitem_() method.
- It returns an iterator or can take indexes (an object that can generate an iterator).
- It produce items on demand.
- lists, tuples, dictionaries, and sets are built-in iterables.
Iterator
- An object that allows you to iterate over collections of data such list.
- it should implement a _next_() method and _iter_() methods that returns an item in every call.
- Iterators take responsibility for two main actions:
- Returning the data from a stream or container one item at a time
- Keeping track of the current and visited items
- Every Iterator is an interable but the other way around is not always true.
Generators
- Generators are iterators, but you can only iterate over them once.
- Generators do’not store all the values in memory, they generate the values on the fly.
- You use them by iterating over them, either with a ‘for’ loop or by passing them to any function or construct that iterates.
- There are two ways generators:
- Using functions using yield instead of return
- Uisng Generator expression
Implementing a generator
class Myiterator():
def __init__(self, sequence):
self.sequence = sequence
self.next = 0
def __iter__(self):
return self
def __next__(self):
if self.next < len(self.sequence):
temp = self.sequence[self.next]
self.next += 1
return temp
else:
raise StopIteration
a = [1, 2, 3, 4]
for item in Myiterator(a):
print(item)
1
2
3
4
Generator using function
def generator_function():
for i in range(4):
yield i
for item in generator_function():
print(item)
0
1
2
3
Generator expression \(\Longrightarrow\) (expression for item in iterable)
My_Generator = (i ** 2 for i in range(4))
for i in My_Generator:
print(i)
0
1
4
9
Pipelining Generators
- Multiple generators can be used to pipeline a series of operations.
def fibonacci_numbers(nums):
x, y = 0, 1
for _ in range(nums):
x, y = y, x+y
yield x
def square(nums):
for num in nums:
yield num**2
print("Finding the sum of squares of numbers in the Fibonacci series")
print(sum(square(fibonacci_numbers(10))))
Finding the sum of squares of numbers in the Fibonacci series
4895
Packages and Modules (file.py)
Using import
- Importing Modules
- Modules refer to a file containing Python statements and definitions.
- A file containing Python code is called a module. For example: My_Module.py is a module where its module name would be My_Module.
import os, time # Import ‘os’ and ‘time’ modules
import numpy as np # Import numpy module and changing its name to _np_ for less writing
from yaml.loader import SafeLoader # Import a function called "Safeloader" from module "yaml"
Decorators
- A Python decorator is a function that takes in a function and returns it by adding some functionality (link).
- Python uses @ to define a decorator.
def zero_devision(func):
# define the inner function
def inner(a, b):
# add some additional functionality to the decorated function
if b==0:
print("Cannot devide by zero !!!")
return
# return the inner function
return func(a, b)
return inner
@zero_devision
def devide(a, b):
return a/b
print("==== deviding by non-zero =====")
print(devide(4,3))
print("\n==== deviding by zero =====")
devide(4,0)
==== deviding by non-zero =====
1.3333333333333333
==== deviding by zero =====
Cannot devide by zero !!!
Type Hints
Numpy
- Numpy is the core library for scientific computing in Python.
- It provides a high-performance multidimensional array object and tools for working with these arrays.
- Optimized library for matrix and vector computation.
- Makes use of C/C++ subroutines and memory-efficient data structures.
- Lots of computation can be efficiently represented as np.ndarray.
- This is the data type that you will use to represent matrix/vector computations.
- Constructor function is np.array()
Arrays
- A numpy array is a grid of values, all the same type, and is indexed by a tuple of nonnegative integers.
- The number of dimensions is the rank of the array
- The shape of an array is a tuple of integers giving the size of the array along each dimension.
- One can initialize numpy arrays from nested Python lists.
- The first dimension (axis = 0) denotes the rows.
- The second dimension (axis = 1) denotes the columns.
import numpy as np
x = np.array([1,2,3]) # Create a rank 1 array
y = np.array([[3,4,5]]) # Create a rank 2 array
z = np.array([[1], [2], [3]]) # Create a rank 2 array
t = np.array([[6,7],[8,9]]) # Create a rank 2 array
print("x:", x)
print("y:", y)
print("z:", z)
print("===== shape of arrays =====")
print(x.shape)
print(y.shape)
print(z.shape)
print(t.shape)
x: [1 2 3]
y: [[3 4 5]]
z: [[1]
[2]
[3]]
===== shape of arrays =====
(3,)
(1, 3)
(3, 1)
(2, 2)
Numpy has many functions to create different arrays:
- Two functions for creating 1-D arrays:
python np.linspace(stat, stop, # samples)
\(~\Longrightarrow\) Returns evenly spaced numbers over a specified interval.python np.arange(stat, stop, step)
\(~\Longrightarrow\) Return evenly spaced values within a given interval.- step: spacing between values
a = np.linspace(-5, 5, 100)
print("==== a shape linspace ====")
print(a.shape)
b = np.arange(-5, 5, 0.1)
print("==== a shape arange ====")
print(b.shape)
==== a shape linspace ====
(100,)
==== a shape arange ====
(100,)
- Functions for creating 2-D arrays (matrix)
print("==== an array of all zeros ====")
a = np.zeros((2,2))
print(a)
print("==== an array of all ones ====")
b = np.ones((1,2))
print(b)
print("==== an array of constant values ====")
c = np.full((2,2), 7)
print(c)
print("==== a 2x2 identity matrix ====")
d = np.eye(2)
print(d)
print("==== an array with random entries ====")
e = np.random.random((2,2))
print(e)
==== an array of all zeros ====
[[0. 0.]
[0. 0.]]
==== an array of all ones ====
[[1. 1.]]
==== an array of constant values ====
[[7 7]
[7 7]]
==== a 2x2 identity matrix ====
[[1. 0.]
[0. 1.]]
==== an array with random entries ====
[[0.96780339 0.1153085 ]
[0.78520961 0.12827767]]
Data Types
- Numpy arrays are a collection of elements of the same type.
- Numpy provides a large set of numeric datatypes that can be used to construct arrays.
- Numpy tries to guess a datatype when an array is created.
- Functions that construct arrays usually include an optional argument to explicitly specify the data type.
x = np.array([3, 4]) # numpy choose the datatype
print(x.dtype)
x = np.array([1.3, 2.0]) # numpy choose the datatype
print(x.dtype)
x = np.array([4, 2], dtype=np.int64) # User datatype
print(x.dtype)
int64
float64
int64
Math Operations
- max()/min() operation along one axis:
- Argument “axis” controls the direction of operation
- For example, “axis = 1” means taking max() operation row-wise (recall axis = 1 denotes rows)
- This results in taking max() between entries of a row (different columns)
- If the original array has two dimensions (i.e., a \(m\times n\) matrix), then the max() output is a rank 1 array with dimension written as (m,)
- “keepdims = True” argument forces the output to have the exact shape (dimension) before applying the operation.
- “keepdims” is the argument for many other math operations such as np.min, np.sum(), np.mean(), etx
x = np.array([[1, 2, 3],[4, 5, 6]])
print("x shape:", x.shape)
print("=======================")
xm1 = np.max(x, axis = 1)
print(xm1, xm1.shape)
print("=======================")
xm2 = np.max(x, axis = 1, keepdims = True)
print(xm2, xm2.shape)
print("=======================")
y = np.array([[[1, 2, 3],[4, 5, 6]], [[-1, 3, 2],[0, 1, 2]]])
print("y shape:", y.shape)
print("=======================")
ym1 = np.max(y, axis = 1)
print(ym1, ym1.shape)
print("=======================")
ym2 = np.max(y, axis = 1, keepdims=True)
print(ym2, ym2.shape)
x shape: (2, 3)
=======================
[3 6] (2,)
=======================
[[3]
[6]] (2, 1)
=======================
y shape: (2, 2, 3)
=======================
[[4 5 6]
[0 3 2]] (2, 3)
=======================
[[[4 5 6]]
[[0 3 2]]] (2, 1, 3)
- Transpose and reshape operation:
- “-1” in one axis means everything left. For example,
python x.reshape((1,-1)
reshapes the \(x\) dimension such that the first dimension has one and the second dimension has 3 elements. - Taking the transpose of a rank 1 array does nothing (printing the same thing).
- “-1” in one axis means everything left. For example,
x = np.array([[1, 2, 3],[4, 5, 6]])
print("==== x shape =====")
print(x.shape)
print("==== x.T shape =====")
print(x.T.shape)
print("==== reshape(1, -1) =====")
print(x.reshape((1, -1)).shape)
print("==== reshape(-1, 1) =====")
print(x.reshape((-1, 1)).shape)
xx = np.array([[1, 2, 3]])
print("==== reshape(1, 2, 3) =====")
print(xx.shape)
==== x shape =====
(2, 3)
==== x.T shape =====
(3, 2)
==== reshape(1, -1) =====
(1, 6)
==== reshape(-1, 1) =====
(6, 1)
==== reshape(1, 2, 3) =====
(1, 3)
- Infix operators (i.e. +, -, *, **, /) are element-wise.
- Matrix Operations: np.dot, np.linalg.norm, .T, +, -, *, …
- Matrix multiplication is done with np.dot(x, W) or x.dot(W). Transpose a matrix with x.T
- Note: Shapes \((N,) != (1, N) != (N,1)\)
x = np.array([[4,2],[1,9]], dtype=np.float64)
y = np.array([[2,1],[3,9]], dtype=np.float64)
print("==== Elementwise sum ====")
print(x + y)
print(np.add(x, y))
print("==== Elementwise difference ====")
print(x - y)
print(np.subtract(x, y))
print("==== Elementwise product ====")
print(x * y)
print(np.multiply(x, y))
print("==== Elementwise division ====")
print(x / y)
print(np.divide(x, y))
print("==== Elementwise square root ====")
print(np.sqrt(x))
print("==== Elementwise power ====")
print(np.power(x, 2))
print(x**2)
x = np.array([[3,1],[2,-2]])
y = np.array([[3,4],[5,9]])
v = np.array([9,0])
w = np.array([3, 8])
print("==== Inner product of vectors ====")
print(v.dot(w))
print(np.dot(v, w))
# The result is the rank 1 array
print("==== Matrix/vector product ====")
print(x.dot(v))
print(np.dot(x, v))
# The result is the rank 2 array)
print("==== Matrix/matrix product ====")
print(x.dot(y))
print(np.dot(x, y))
x = np.array([[1,2],[3,4]])
print("==== Compute sum of all elements ====")
print(np.sum(x))
print("==== Compute the sum of each column ====")
print(np.sum(x, axis=0))
print("==== Compute sum of each row ====")
print(np.sum(x, axis=1))
x = np.array([[3,1],[2,-2]])
y = np.array([[3,4],[5,9]])
print("==== Compute the matrix-matrix multiplication using np.matmul() =====")
print(np.matmul(x, y))
print("==== Compute the matrix-matrix multiplication using np.dot() =====")
print(np.dot(x, y))
x = np.array([[4,1],[3,2]])
print("==== Frobenius norm ====")
print(np.linalg.norm(x))
print("==== nuclear norm ====")
print(np.linalg.norm(x, 'nuc'))
print("==== 2 norm ====")
print(np.linalg.norm(x, 2))
==== Elementwise sum ====
[[ 6. 3.]
[ 4. 18.]]
[[ 6. 3.]
[ 4. 18.]]
==== Elementwise difference ====
[[ 2. 1.]
[-2. 0.]]
[[ 2. 1.]
[-2. 0.]]
==== Elementwise product ====
[[ 8. 2.]
[ 3. 81.]]
[[ 8. 2.]
[ 3. 81.]]
==== Elementwise division ====
[[2. 2. ]
[0.33333333 1. ]]
[[2. 2. ]
[0.33333333 1. ]]
==== Elementwise square root ====
[[2. 1.41421356]
[1. 3. ]]
==== Elementwise power ====
[[16. 4.]
[ 1. 81.]]
[[16. 4.]
[ 1. 81.]]
==== Inner product of vectors ====
27
27
==== Matrix/vector product ====
[27 18]
[27 18]
==== Matrix/matrix product ====
[[ 14 21]
[ -4 -10]]
[[ 14 21]
[ -4 -10]]
==== Compute sum of all elements ====
10
==== Compute the sum of each column ====
[4 6]
==== Compute sum of each row ====
[3 7]
==== Compute the matrix-matrix multiplication using np.matmul() =====
[[ 14 21]
[ -4 -10]]
==== Compute the matrix-matrix multiplication using np.dot() =====
[[ 14 21]
[ -4 -10]]
==== Frobenius norm ====
5.477225575051661
==== nuclear norm ====
6.324555320336758
==== 2 norm ====
5.398345637668169
Indexing
- Similar to Python lists, numpy arrays can be indexed by slicing or itegers.
- Different ways for indexing:
- Single element indexing
- Slicing indexing
- Integer indexing
- Mixed indexing
- Single Element Indexing
- Single element indexing works exactly like that for other standard Python sequences.
- Modifying an array with single element indexing will also modify the original array.
- It is 0-based, and accepts negative indices for indexing from the end of the array.
- It is not necessary to separate each dimension’s index into its own set of square brackets.
- Separating each dimension’s index into its own set of square brackets is recommended.
x = np.arange(10)
print("x=", x)
print(x[2])
print(x[-2])
print("==== changing the shape of x to (2, 5) ====")
x.shape = (2, 5)
print("x=", x)
print(x[1, 3] == x[1][3])
print("==== The first row of x and its shape")
print(x[0], x[0].shape)
x= [0 1 2 3 4 5 6 7 8 9]
2
8
==== changing the shape of x to (2, 5) ====
x= [[0 1 2 3 4]
[5 6 7 8 9]]
True
==== The first row of x and its shape
[0 1 2 3 4] (5,)
- Slicing Indexing
- We need to specify a slice for each dimension of the array.
- Slice has the format as
start:stop:step
- Slice has the format as
- Modifying a slice of an array will also modify the original array.
- Mixing integer indexing with slices results in an array of lower rank.
- Indexing using slices results in an array of the same rank as the original array.
- For slicing method, do not use separate square brackets for each dimension.
- We need to specify a slice for each dimension of the array.
a = np.array([[5,6,1,3], [2,-6,0,9], [19,-10,1,2]])
print("a=", a, a.shape)
print("===== A slice of array a =====")
b = a[:2, 1:3]
print(b.shape)
print("a[:2, 1:3] != a[:2][1:3]")
print("===== Entry (0, 1) of array a =====")
print(a[0, 1])
print("===== Modifying entry (0, 0) of b will change the Entry (0, 1) of array a =====")
b[0, 0] = 77
print(a[0, 1])
print("===== Rank 1 view of the second row of array a ======")
row_r1 = a[1, :]
print(row_r1, row_r1.shape)
print("===== Rank 2 view of the second row of array a ======")
row_r2 = a[1:2, :] # Rank 2 view of the second row of a
print(row_r2, row_r2.shape)
print("==== slice is selected only for the first dimension")
t = a[1:2]
print(t, t.shape)
print("===== Rank 1 view of the second column of array a ======")
col_r1 = a[:, 1]
print(col_r1, col_r1.shape)
print("===== Rank 2 view of the second column of array a ======")
col_r2 = a[:, 1:2]
print(col_r2, col_r2.shape)
a= [[ 5 6 1 3]
[ 2 -6 0 9]
[ 19 -10 1 2]] (3, 4)
===== A slice of array a =====
(2, 2)
a[:2, 1:3] != a[:2][1:3]
===== Entry (0, 1) of array a =====
6
===== Modifying entry (0, 0) of b will change the Entry (0, 1) of array a =====
77
===== Rank 1 view of the second row of array a ======
[ 2 -6 0 9] (4,)
===== Rank 2 view of the second row of array a ======
[[ 2 -6 0 9]] (1, 4)
==== slice is selected only for the first dimension
[[ 2 -6 0 9]] (1, 4)
===== Rank 1 view of the second column of array a ======
[ 77 -6 -10] (3,)
===== Rank 2 view of the second column of array a ======
[[ 77]
[ -6]
[-10]] (3, 1)
- Integer Indexing
- Integer array indexing allows selection of arbitrary items in the array based on their N-dimensional index.
- Each integer array represents a number of indices into that dimension.
- Mixing integer indexing with slices results in an array of lower rank.
- Indexing using slices results in an array of the same rank as the original array.
x = np.arange(10, 1, -1)
print("x=", x)
print("==== A subset of x with specified location =====")
print("subset = ", x[np.array([3, 3, 1, 8])])
print("==== Integer indexing with constructing index array using np.arrange() ====")
print("subset = ", x[[3, 3, 1, 8]])
print("==== A new 3 by 2 array =====")
b = np.array([[1,2], [3, 4], [5, 6]])
print("b=", b, b.shape)
print("==== A subset of b with specified locations =====")
t = b[[0, 1, 2], [0, 1, 0]]
print("t =", t, t.shape)
print("==== t array with another approach =====")
print(np.array([b[0, 0], b[1, 1], b[2, 0]]))
print("==== Integer indexing with constructing index array using np.arrange() ====")
print(b[[0, 0], [1, 1]])
print("==== A new 4 by 3 array =====")
c = np.array([[1,2,3], [4,5,6], [7,8,9], [10, 11, 12]])
print("c =", c)
print("==== Creating an array of indices =====")
i = np.array([0, 2, 0, 1])
print("i = ", i)
print("==== Select one element from each row of c using the indices in i ====")
print(c[np.arange(4), i])
print("==== Mutating one element from each row of c using the indices in i ====")
c[np.arange(4), i] += 10
print("c = ", c)
x= [10 9 8 7 6 5 4 3 2]
==== A subset of x with specified location =====
subset = [7 7 9 2]
==== Integer indexing with constructing index array using np.arrange() ====
subset = [7 7 9 2]
==== A new 3 by 2 array =====
b= [[1 2]
[3 4]
[5 6]] (3, 2)
==== A subset of b with specified locations =====
t = [1 4 5] (3,)
==== t array with another approach =====
[1 4 5]
==== Integer indexing with constructing index array using np.arrange() ====
[2 2]
==== A new 4 by 3 array =====
c = [[ 1 2 3]
[ 4 5 6]
[ 7 8 9]
[10 11 12]]
==== Creating an array of indices =====
i = [0 2 0 1]
==== Select one element from each row of c using the indices in i ====
[ 1 6 7 11]
==== Mutating one element from each row of c using the indices in i ====
c = [[11 2 3]
[ 4 5 16]
[17 8 9]
[10 21 12]]
Broadcasting (link)
- Broadcasting is a super useful mechanism that allows numpy to work with arrays of different shapes.
- For instance, we have a smaller array and a larger array, and we want to use the smaller array multiple times to perform some operation on the larger array.
- When operating on two arrays, NumPy compares their shapes element-wise.
- It starts with the trailing dimensions, and works its way forward.
- Two dimensions are compatible when
- They are equal
- One of them is 1
x = np.ones((3, 4)) # Random (3, 4) matrix
y = 2*np.ones((3, 1)) # Random (3, 1) matrix
print("x = ", x, x.shape)
print("y = ", y, y.shape)
print("======= x+y =====")
print(x+y)
print("=================")
z = -2*np.ones((1, 4)) # Random (3,) vector
print("z = ", z)
print("====== shape of x + y =======")
print((x + y).shape) # Adds y to each column of x
print("====== shape of x * y =======")
print((x * z).shape) # Multiplies z element-wise with each row of x
print("====== shape of x + y.T =======")
print((y + y.T).shape)
print("====== shape of y + y.T =======")
print((y + y.T).shape)
x = [[1. 1. 1. 1.]
[1. 1. 1. 1.]
[1. 1. 1. 1.]] (3, 4)
y = [[2.]
[2.]
[2.]] (3, 1)
======= x+y =====
[[3. 3. 3. 3.]
[3. 3. 3. 3.]
[3. 3. 3. 3.]]
=================
z = [[-2. -2. -2. -2.]]
====== shape of x + y =======
(3, 4)
====== shape of x * y =======
(3, 4)
====== shape of x + y.T =======
(3, 3)
====== shape of y + y.T =======
(3, 3)
-
Broadcasting two arrays together follows these rules (link):
- If the arrays do not have the same rank, prepend the shape of the lower rank array with 1s until both shapes have the same length.
- The two arrays are said to be compatible in a dimension if they have the same size in the dimension, or if one of the arrays has size 1 in that dimension.
- The arrays can be broadcast together if they are compatible in all dimensions.
- After broadcasting, each array behaves as if it had shape equal to the elementwise maximum of shapes of the two input arrays.
- In any dimension where one array had size 1 and the other array had size greater than 1, the first array behaves as if it were copied along that dimension
Avoid explicit for-loops over indices/axes at all costs.
-
For-loops will dramatically slow down your code (~10-100x)
-
Sqaurring each elemt of a mtrix using for loops and power operation
import time
s = time.time()
x = np.random.rand(1000,1000)
for i in range(x.shape[0]):
for j in range(x.shape[1]):
x[i,j] **= 2
print("Using for loop ===== ", f"{time.time()-s:.4f} second")
s = time.time()
x **= 2
print("Using power operation ===== ", f"{time.time()-s:.4f} second")
Using for loop ===== 0.4877 second
Using power operation ===== 0.0012 second
- Adding a constant to a matrix using for loops and broadcasting
s = time.time()
for i in range(100, 1000):
for j in range(x.shape[1]):
x[i, j] += 5
print("Using for loop ===== ", f"{time.time()-s:.4f} second")
s = time.time()
x[np.arange(100,1000), :] += 5
print("Using broadcasting ===== ", f"{time.time()-s:.4f} second")
Using for loop ===== 0.4557 second
Using broadcasting ===== 0.0021 second
Pandas
import pandas as pd
pd.Series({"Ali": 3, "Abby": 4})
Ali 3
Abby 4
dtype: int64
Matplotlib
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline
# %matplotlib
x = np.linspace(-5, 5, 100)
y1 = 1/(1+np.exp(-x))
y2 = np.log(1+np.exp(-x))
x.shape, y1.shape, y2.shape
((100,), (100,), (100,))
plt.plot(x, y1, 'r-', x, y2, 'b--', linewidth=2)
plt.grid()
plt.legend(['Sigmoid Function', 'Exp Function'])