Download as pdf or txt
Download as pdf or txt
You are on page 1of 385

My Book of

Python
Computing
(Python 3)

Abhijit Kar Gupta

Copyright ©reserved by the Author

Book: My Book of Python Computing Author: Dr. Abhijit Kar Gupta Cover
Design: Dr. Dipankar Ghosh

April 2021, Kolkata


Preface

Every enchanting journey begins from the beginning. This book offers an
enjoyable and comfortable journey to a beginner. The ideas of Core Python
are introduced in a lucid way. The sections and chapters are supplemented
with numerical examples and graphical demonstrations. Some useful external
packages are Writing clean and minimal codes introduced. in Python

becomes highly effective with various external modules and packages. The
experience of data handling turns magical, and doing scientific computation
becomes greatly simplified. I hope, the students, teachers and any interested
person may be benefitted by this book.

Let me profusely thank all my students, colleagues, friends, and near and dear
ones for supporting me in this project of writing this book. Hope, the keen
readers enjoy reading.

Thank you.
Abhijit Kar Gupta, Kolkata The
Zen of Python

Beautiful is better than ugly.


Explicit is better than implicit. Simple is better than complex.
Complex is better than complicated. Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity. Errors should never pass silently. Unless
explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -let's do more of those!

Copyright

This document has been placed in the public domain. Source:


https://github.com/python/peps/blob/master/pep
-0020.txt

Brief Content
Core Python

Chapter 1: Fundamental Ideas 1


Chapter 2: Built-in Modules 53
Chapter 3: Loops and Conditions 91
Chapter 6: Python Function 115
Chapter 5: Data Structures 141
Chapter 6: Class, Object, Module 229
Chapter 7: Drawing with Turtle 257

Python Computation with External Packages

Chapter 8: NumPy: Numerical Python 297


Chapter 9: Matplotlib: Plotting Library 440
Chapter 10: SymPy: Symbolic Python 525
Chapter 11: Pandas: Structured Data 563
Elementary Python Codes 583

Appendix 661
Bibliography 675
Index 679

TABLE OF CONTENTS
Core Python
CHAPTER 1: Fundamental Ideas 1

1.1 Variables, Numbers, Operators 5


1.1.1 Variables 5
1.1.2 Numbers 6
1.1.3 Operators 8
1.2 Typing on Python Interpreter 10 1.2.1 Writing a Single Line 11 1.2.2
Writing Multiple Lines 14 1.3 Namespace 16 1.4 Python as Calculator 21
1.4.1 Op with Real Numbers 21 1.4.2 Op with Complex Numbers 23 1.4.3
Op with Boolean Numbers 24 1.4.4 Comparison of Numbers 25 1.5 Types
and Identities 25 1.5.1 Type 26 1.5.2 Identity 28 1.6 Built-in Functions 31 1.7
Numbers in Different Bases 37 1.8 Input and Output (I/O) 38 1.8.1 Input
Styles 38 1.8.2 Output Styles 40 1.8.3 Input from Outside 41 1.8.4 Evaluate
the Input String 42 1.8.5 Formatted Output 44 1.9 Writing Codes 46 1.10
Read/ Write Data 49 1.11 Commenting 51 1.11.1 Comment a Single Line 51
1.11.2 Comment a Block of Lines 52

CHAPTER 2: Built-in Modules 53


2.1 What is a Module? 53
2.2 Import Module 54
2.3 Math Module 58
2.4 Complex Math Module 66
2.5 Module for Random Numbers 70
2.5.1 Generate Random Numbers73
2.5.2 Random Choice 77
2.5.3 Random Sampling 80
2.5.4 Random Shuffling 81
2.5.5 Cryptographically… 81
2.6 Time, Date, Calendar 83

CHAPTER 3: Loops and Conditions 91 3.1 Loops 91 3.1.1 For-Loop 91

3.1.2 While Loop 100


3.2 Conditions 102
3.3 Try Except 107

CHAPTER 4: Python Function 115


4.1 Define a Function 115

4.1.1 Function with Doc-String 119


4.1.2 Void Function 121
4.1.3 Some Trivial Functions 123
4.1.4 Functions with Arbitrary Number of Arguments 124
4.1.5 Functions with Keyword Arguments 125
4.1.6 …Arbitrary Number of
Keyword Arguments 127
4.2 Local and Global Parameters 129
4.3 Function Inside Function 132
4.4 Recursive Function 133
4.5 Lambda Function 134
4.6 Function Overload 135
4.7 About Imported Functions 137
4.8 Python Map Function 138
4.9 Python Filter Function 138
4.10 Python zip Function 140
CHAPTER 5: Data Structures 141

5.1 List 142


5.1.1 Indexing 145
5.1.2 Slicing 146
5.1.3 Built-in Functions on List 147
5.1.4 List Methods 150
5.1.5 List Creator 163
5.1.6 List Comprehension 165
5.1.7 Nested List 168
5.2 String 170
5.2.1 Indexing 171
5.2.2 Slicing 171
5.2.3 Built-in Functions on… 174
5.2.4 Concatenation 176
5.2.5 Methods in String 177
5.2.6 String Format 185
5.2.7 String to List 186
5.3 Tuples 187
5.3.1 Built-in Functions… 187
5.3.2 Indexing 188
5.3.3 Slicing 189
5.3.4 Concatenation 189
5.3.5 Conversion 189
5.3.6 Methods 190
5.4 Set 192
5.4.1 Built-in Function on Set 194
5.4.2 Methods 194
5.4.3 Adding Two Sets 197
5.4.4 Set Theoretic Operations 198
5.4.5 Subset 199
5.4.6 Frozen Set 200
5.5 Dictionary 201
5.5.1 Built-in Functions… 202
5.5.2 Methods on Dictionary 203
5.5.3 Create a Dictionary 210
5.5.4 Dictionary Comprehension 211
5.5.5 Nested Dictionary 212
5.6 Packing, Unpacking 214
5.7 Compare and Search 216
5.8 Copy Objects 218

CHAPTER 6: Class, Object, Module 229


6.1 Class/ Object 230

6.1.1 Deleting Objects … 244


6.1.2 Inheritance 246
6.1.3 Class with Iterator 248
6.2 Module 251
6.3 Package 255

CHAPTER 7: Drawing with Turtle 257


Python Computation with External Packages

CHAPTER 8: NumPy: Numerical … 297


8.1 NumPy Arrays 298

8.1.1 Built-in Functions … 300


8.1.2 Methods and Attributes 301
8.1.3 Higher Dimensional Arrays 302
8.1.4 About Data Type 305
8.1.5 Properties of Complex… 312
8.1.6 Methods of Creation… 313
8.1.7 Shape, Reshape, Size,… 319
8.1.8 Flatten, Ravel 321
8.1.9 Unique Elements 324
8.1.10 Iterator on Array 326
8.1.11 Indexing 327
8.1.12 Slicing 328
8.1.13 Swap two Elements 332
8.2 NumPy Methods on Arrays 334
8.2.1 Checking all Elements 334
8.2.2 Where is the Element? 335
8.2.3 Put/Insert new Element 336
8.2.4 Delete Element 340
8.2.5 Split Arrays 341
8.2.6 Append Values 344
8.2.7 Concatenate 346
8.2.8 Stacking Arrays 348
8.2.9 Methods of Statistics 351
8.2.10 Product, Difference 357
8.2.11 Trace 362
8.2.12 Sorting 363
8.3 Arithmetic Operations 367
8.4 Broadcasting 370
8.5 Transpose, Flip, Rotate 372
8.6 Matrix Object 374
8.6.1 Complex Matrix 375
8.7 Special Arrays 377
8.8 Product of Arrays 385
8.8.1 Product of Multi-Dimensional Arrays 387
8.8.2 Matrix like Product 388
8.8.3 Tensor Operations 390
8.9 NumPy Functions 397
8.10 Vectorize a Function 403
8.11 Polynomial in NumPy 405
8.11.1 Arithmetic Operations 406
8.11.2 Functions of polynomials 407
8.11.3 Roots of polynomial… 407
8.11.4 Derivative 408
8.11.5 Indefinite Integral 409
8.12 Scientific Modules in NumPy 409
8.12.1 NumPy Polynomial… 410
8.12.2 Linear Algebra… 411
8.13 Grid by NumPy Arrays 419
8.14 Load and Save Data File 422
8.15 Random Numbers in NumPy 425

CHAPTER 9: Matplotlib 440

9.1 Plotting in 2D 441


9.1.1 X-Y plots 441
9.1.2 Polar plot 465
9.1.3 Plot Data from a File 467
9.1.4 Save Figure in a File 471
9.1.5 Greek letters, Symbols,… 472
9.1.6 Multiple Plots in One… 475
9.1.7 Multiple Figures 482
9.1.8 Subplot 484
9.1.9 Designing Grid Space 486
9.1.10 Statistical Plots 488
9.1.11 Contour Plot 500
9.2 3D plot 514

CHAPTER 10: SymPy: Symbolic… 525

10.1 Symbols, Expressions, Calc … 527


10.2 Matrix 536
10.3 Calculus 541
10.3.1 Limit 541
10.3.2 Differentiation 541
10.3.3 Integration 543
10.4 Plotting a function 544
10.4.1 Plot with Matplotlib 545
10.4.2 Plot with SymPy Plotting.. 546
10.5 Geometrical Objects 554
10.6 Solving Differential Equations 556

CHAPTER 11: Pandas: Structured.. 563

11.1 Series 564


11.2 DataFrame 569
11.2.1 …Data as DataFrame 577

Elementary Python Codes 583


Appendix 661
Bibliography 675
Index 679
Core Python
CHAPTER

1
Fundamental Ideas

“The joy of coding Python should be in seeing short, concise, readable


classes that expresses a lot of action in a small amount of clear code – not in
terms of trivial code that bores the reader to death.” - Guido van Rossum

This book follows Python 3 syntaxes. Python is a high-level computer


language which has now becomeone of World’s most popular programming
languages. The syntaxes are very simple and minimal so that we can write
clean and beautiful Python codes. There are several other advantages over the
other existing high-level languages that we shall eventually know.

Python has become exceptionally adaptable language of our time.


Tremendous applications are found in Basic Science, Engineering, Data
Science, Data Management, Machine learning (ML), Deep Learning,
Networking, Web Applications etc. Enormous number of scientific modules
have been developed. Python is an open-source programming language with
huge community support.

Unlike FORTRAN, C, C++, and other languages, it is an interpreter-based


language. As we keep writing each line on the Python interpreter or Python
shell, it is being executed (or interpreted) in contrast to most other languages
where the entire program must be compiled first, at one go. Many often, of
course, we need to write a standard program or code which is usually longer
than a few lines. In that case, we write the programs (Python scripts) in a file
and then execute (‘run’) it. Computing in Python works with both functional
programming and Object-Oriented Programming (OOP) ways.

Why Python? In short:


• Powerful programming language, easy to learn
• Efficient high dimensional Data Structure
• Clean Syntax, Code readability
• Object Oriented, imperative, functional computation
• Programs are portable.
• Open Source
• Great Community Support
• Interpreter based language

What is about the name Python?

The name ‘Python’ is not from a snake (as the Python logo might suggest)!
The name ‘Python’ originated from the famous BBC comedy serial ‘Monty
Python’. Guido van Rossum, the originator of Python, is very fond of this
popular comedy serial. So, Guido kept this name as he finished his project of
writing a new computing language.

How to install Python?

Regarding installation and versions of Python, see Appendix. We can install


any version. There are only a very few differences between the two versions
(Python 2.x and Python3.x) from user’s point of view. Some discussions in
this regard are also made in Appendix A. In this edition of the book, we
follow Python 3.x version (In the first edition, we followed Python 2.x.). One
can install (and uninstall) any version easily on the machine and then can
begin to type. In fact, the two versions may also coexist on the same machine.

You may directly go to the official site of Python (www.python.org) and


download any stable version. Later, you may decide to install external
packages as and when required. Also, there are some distributions like
Anaconda, Python (x, y) etc. which come with core Python along with some
common external packages (e.g.,NumPy, SciPy, Matplotlib).

Before we begin to write a Python code, we should be familiar with the


essential tools– syntaxes, variables, numbers, operators, methods, logical and
loop structures, data structures, and other ideas.

1.1 Variables, Numbers, Operators


Writing a Python program is easy. It is very close to the human language and
it follows the logic that we understand. Python comes with minimal syntaxes.
We shall try to write beautiful and clean codes with Python.

1.1.1 Variables

Name of a variable (or any identifier such as class, function, module, or other
object) can be anything combined with letters, numbers, and some symbols
like underscore (_) in the keyboard. The special symbols @, $, %, * etc. and
the mathematical symbols (+, -, / etc.) , cannot be used. Variables are to begin
with a letter (the upper case or lowercase letters from A to Z) and the names
cannot start with a number. The names of the variables are, of course, case
sensitive. The variable names, ‘Xy’, ‘xy’, ‘xY’ are all different. But the
names cannot be the words given in the following table which are used for
system commands and functions. Those are Python keywords (in lower case
letters) reserved for the use by the system.
Reserved words:

and, assert, break, class, continue, def, del, elif, else, except, exec, finally, for,
from, global, if, import, in, is, lambda, not, or, pass, print, raise, return, try,
while, with, yield

1.1.2 Numbers

There are three different kinds of numbers that a computer program usually
handles: integer, float (real numbers with decimal part) and complex (�� +
����, �� =√−1). Python supports all three kinds.

I ntegers in Python 3 are of unlimited size. In fact, any large integer of any
length which may even occupy the entire memory space of the processor is
possible. [Python 2 has two integer types: - int andlong. There is no 'long
integer' type in Python 3 anymore.] In addition, Booleans are a subtype of
plain integers.

• Integer
[Examples: 236, -123]
• Float (or floating point
numbers)
[Examples: 13.25, 0.0, -13.23, 1.2e-10, 3.85e12 ]
• Complex
[Examples: 5j, 9.8j, 3+2j, 1.2e-10j]
• Long
[Examples: Any integer that ends with ‘l’ or ‘L’. 123L will be treated as long
in Python 2.]
• Boolean
True, False

Note:

The integers are positive or negative whole numbers. Long integers are
integers of unlimited size (written as integers followed by upper- or lower-
case L). The Max value of the default int in Python 2 is 65535, anything
above that will be a long.

In the calculations, as it often happens that there are all kinds of numbers
mixed up. The system will understand in what number the result will be
delivered, in a clever way. If all the numbers are integers, the result is an
integer. If at least one is real in the mixture of real and integer, the result will
be in real. If there is a presence of a complex number, the result will be in
complex. Of course, we can explicitly set the type of a variable to ‘integer’,
‘real’ or ‘complex’.

1.1.3 Operators

Mathematical Operators
Consider two variables ‘��’ and ‘��’. The following operations are with
�� = 10, �� = 2

Addition (+): �� + �� = 12 Subtraction (-): �� – �� = 8


Multiplication (*): �� ∗ �� = 20 Division(/): ��/�� = 5.0

Modulo (%): ��%�� = 0 [Remainder: when �� is divided by ��]


Power or Exponent (**): �� ∗∗ �� = 100
Floor Division (//):
Example,7.5//2 = 3.0, where 7.5/2 = 3.75
Apart from mathematical operators we encounter other kinds of operators
too.
Comparison Operators

== Equal to
!= Not equal to
<> Less than and Greater than < Less than
> Greater than
>= Greater than equal to <= Less than equal to

Membership Operators in, not, as

1.2 Typing on Python Interpreter Hello World!

Let us begin to type on Python IDLE interpreter (>>>) or command line


[IDLE = Integrated Development and Learning Environment]. We type some
line or some instruction on the interpreter and then‘Enter’ key to execute. The
line that appears below is the output or some message that it returns.
[Sometimes, nothing returns as output which indicates that the instruction is
accepted without error. Else, we may get an error message.]

We must type anything from the start (from the first column) on interpreter or
inside any Python shell. We are not allowed to press ‘space bar’ before we
write. In the latter case, it will raise error.

Each line is an instruction. A set of Python instructions will form a Python


code. We can write the lines in a file which is usually called Python script.
Python scripts (programs or codes) are automatically saved with extension
.py. Example: somefile.py

Learning of every Computer language begins with typing ‘Hello World’! Let
us also type:
>>> print(‘Hello World!’) Hello World!

Note: print() is a function (a built-in function), in whichwe give the string,


‘Hello World!’ as argument. We get ‘Hello World!’ in return, as output.

1.2.1 Writing a Single Line


A string can be made with single, double, or triple quotes. On the interpreter,
we need not always type print() function to print or write. We can directly
type the string (or anything) and press Enter. However, for strings of various
styles, we shall sometimes notice some differences. Let us write some texts
as strings and check how they return.

>>> print(‘Hello World!’)


Hello World! # Output: Not a string
>>> ‘Hello World!’
‘HelloWorld!’ # Output: String
# Strings of various styles

# Double quotes
>>> “I am a Pythonista.” ‘I am a Pythonista.’

# Triple double quotes


>>> “““I am learning Python.””” ‘I am learning Python.’

# Triple single quotes


>>> ‘‘‘I am writing codes in Python.’’’ ‘I am writing codes in Python.’
# When quotes clash
>>> ‘I a’m writing codes in Python’ SyntaxError: invalid syntax

Note: The syntax error arises because Python treats ‘I a’ as a string from the
above statement and it does not understand the rest of the text. To include the
apostrophe(‘) inside the single quoted string we need to use an escape
character(\).

# Correct way of writing


>>> 'I a\'m writing codes in Python' "I a'm writing codes in Python"
# We can avoid this(\) symbol by typing two different kinds of quotes.
>>> "I a'm writing codes in Python" "I a'm writing codes in Python"
# Connecting multiple lines

>>> 'I am a beginner. \


I wish to learn Python.'
'I am a beginner. I wish to learn Python.'
>>> print('I am a beginner. \ I wish to learn Python.')
I am a beginner. I wish to learn Python.

Note: If we write the first line and then press Enter to write the second line,
that will result in an error (incomplete string). The escape (\) symbol connects
the two lines and make one.

1.2.2 Writing Multiple Lines Let us write the following two lines:

‘I am a beginner.
I wish to learn Python.’

>>> """I am a beginner.


I wish to learn Python."""
'I am a beginner.\nI wish to learn Python.'

>>> print("""I am a beginner. I wish to learn Python.""") I am a


beginner.
I wish to learn Python.

Note: Creating a new line is possible under triple quotes. Of course, this time
we need to use print() function to write properly as we see below. When
written directly over the interpreter, it produced a line with \n in between.
This means, it recognized a new line but could not represent properly
withoutprint().

Inside the triple quotes, we can put escape characters like \t, \n to create a tab-
space horizontally and a new line respectively.

>>> print("""\t I am a beginner. \n \t I wish to learn Python.""") I am a


beginner.

I wish to learn Python.

Exercise: Write the The Zen of Python in the poetry form (with appropriate
spaces and lines) with the above knowledge.

1.3 Namespace
As we open the interpreter (>>>) and type dir(), we see the following in the
directory:

>>> dir()
['__annotations__',
'__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__']

We see some names with double underscore (__) on two sides of a


dictionaries, modules
name. They are built-in and classes written in a

pythonic way. For now, note only one name ‘__builtins__’ which is a built-
in module. Let us check what it contains.

>>> dir(__builtins__)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException',
'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning',
'ChildProcessError',
'ConnectionAbortedError',
'ConnectionError',
'ConnectionRefusedError',
'ConnectionResetError',
'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception',
'False', 'FileExistsError',
'FileNotFoundError',
'FloatingPointError',
'FutureWarning', 'IOError',
'GeneratorExit',

'ImportError', 'ImportWarning', 'IndentationError', 'IndexError',


'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt',
'LookupError', 'MemoryError', 'ModuleNotFoundError', 'NameError', 'None',
'NotADirectoryError',
'NotImplemented',
'NotImplementedError', 'OSError', 'OverflowError',
'PendingDeprecationWarning',
'PermissionError',
'ProcessLookupError',
'RecursionError', 'ReferenceError', 'ResourceWarning', 'RuntimeError',
'RuntimeWarning', 'StopAsyncIteration', 'StopIteration',
'SyntaxWarning',
'SystemExit',
'TimeoutError', 'True', 'TypeError', 'UnboundLocalError',
'UnicodeDecodeError',
'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError',
'SyntaxError', 'SystemError',

'TabError',
'UnicodeWarning', 'ValueError',

'UserWarning', 'Warning', 'WindowsError', 'ZeroDivisionError', '_',


'__build_class__', '__debug__',

'__doc__', '__import__', '__loader__', '__name__', '__package__', '__spec__',


'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'breakpoint', 'bytearray', 'bytes', 'callable',
'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict',
'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit', 'filter', 'float', 'format',
'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int',
'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'map', 'max',
'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property',
'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted',
'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']

Note: The names (in alphabetic order) without underscores:abs, dir, id, list,
type etc. are methods to be applied over Python objects. We can use them
without reference when we write codes. The names with double
underscores(__) on both sides are attributes and methods written in Pythonic
ways. Every Python Class (introduced later in chapter 4) keeps built-in
attributes which can be accessed through dot operator like any other attribute
(to be seen later).

• __doc__ Class documentation string (or none, if undefined).


• __name__ Class name.
• __import__ Built-in function or method which imports modules.

We saw some names in the above display. They are objects. In python,
variables and functions or methods are all objects (We shall formally
introduce the concepts of Class and Object later.). Every object has a name:
some are built-in and some we create. A namespace is a container or
environment which contains unique name for each object in Python with
which we work. [In short, namespace is our working table with all the
necessary tools.]

To understand how new names in the namespace are included, let us type
some names (by assigning two variables to two integers) as follows,

>>> x = 2 >>> y = 4
Now let us type the following: >>> dir()

['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',


'__package__', '__spec__', 'x', 'y']

Note that the namespace (directory) now includes the variable names:x and y
along with the built-in names. Later we will see, apart from __builtins__
module, there are many other modules that we can import into our namespace
for various purposes.

1.4 Python as Calculator


Python interpreter or Python shell can be used as a calculator.

1.4.1 Operations with Real Numbers # Mathematical Operations


>>> 5 + 3.4 # Sum of int and float

9.4
>>> 19 – 6 # Subtraction
13
>>> 12 * 9 # Multiplication
108

>>> 5e2*2.0e-4 with Exponential form 0.1


>>> 58/2 # Division 29.0
>>> 13.5//2 # Floor division 6.0

>>> (52*5 - 6.1)/4 # Simplification 63.475


>>> 2**4 # Power
16

Division: Difference between Python 3 & Python 2 >>> 11/2


5.5
[Python 3: returns a float]

>>> 11/2
5
[Python 2: returns integer (quotient only)]
# Modulo

# Modulo: It returns the remainder >>> 13%5


3
>>> 2%5
2
[Note: 2 < 5, so 2 itself is the Remainder]

# Modulo of a negative number >>> -5 % 2


1
>>> -2 % 5
>>> (3 - 5) % 5
3
>>> (1 - 3*2) % 2
1
[By writing, - 5 = 1 – 3*2]

1.4.2 Operations with Complex Numbers


>>> 3 + 9j (3+9j)

>>> 3 + 2.4j
(3+2.4j) >>> 3 + (2 + 4j) * 2
(9+12j)
>>> (3 + 2j) * (4 + 5j)
(2+23j)

Note: Python computes in the mixed mode. For mathematical operations


involving integer, float, and complex numbers, Python returns the output in
complex. We can think of complex as broader than a float and a float as
broader than an integer. Python will return in the broadest of all.

1.4.3 Operations with Boolean Numbers The Boolean objects, True = 1,


False = 0.

>>> True + True


2
>>> True + False
1
>>> True - False
1
>>> True * False

0
1.4.4 Comparison of Numbers

>>> 2 > 3
False
>>> 3 < 9
True
>>> 5 == 6
False
[Double equal (==) used for comparison]

>>> 6 != 8 # Not equal to True


>>> 5.0 >= 4.99 # Greater than equal True
>>> 10%3 == 1
True

1.5 Types and Identities


The built-in function type() determines the type, id() determines the identity
of an object.

1.5.1 Type

>>> type(89) # Integer <class 'int'>


>>> type(15.67) # Floating point number
<class 'float'>
>>> type(5E2) # 5 × 102 <class ‘float’)
>>> type(5.6e-13) # 5.6 × 10−13

[xey or xEy = �� × 10��] <class 'float'>


>>> type(3 + 5j) # Complex number <class 'complex'>

In Python 2,it is a ‘long’ integer. >>> type(2**80)


<type ‘long’>

>>> z = 2*10 + 5.0 + 3j >>> type(z)


<class 'complex'>
>>> type(2**80)
<class 'int'>

>>> type('Guido') # String <class 'str'>


>>> type('Ananda123') # String <class 'str'>

# Boolean objects: True and False

>>> type(True)
<class 'bool'>
>>> type(False)
<class 'bool'>
>>> int(True)
1
[Integer representation of True] >>> int(False)
0
[Integer representation of False]

# Boolean test

>>> bool(12) # Anything as argument True


>>> bool(0.001)
True
>>> bool(-5928)

True
>>> bool('abc182') True
>>> bool(0) # Zero as argument False
>>> bool() # Nothing as argument False
>>> bool(‘’) # Arg as empty string False

Note: The argument 0 or no argument or some empty object (string or


anything) returns False.
1.5.2 Identity

We can test the identity of an assigned variable. In principle, variable name is


a tag to a number or object. For each input variable, there is an identifier in
the memory location. If we assign another variable to this, the identifier in the
memory gets the same code.

>>> x = 3
[Assign the number 3 to x] # Where is the variable stored?] >>> id(x)
140727110538976 # Memory location >>> y = x # Assign another name

# Check location of the new name >>> id(y)


140727110538976
[Same location as before, only tag changed.]
>>> x = 5 # New number, same tag

# Where is this stored now?


>>> id(x)
140727110539040 # A new location >>> id(y) # What’s about y?
140727110538976
[Location of y does not change.]

# Delete a Variable

# Removed from location >>> del x >>> id(x)

“Traceback (most recent call last):


File "<pyshell#80>", line 1, in <module>
id(x)
NameError: name 'x' is not defined”

Note: The variable ‘x’ is now deleted and now typing id(x) will result in an
error message: “name 'x' is not defined”.

# For typing on IDLE interpreter

Up ( ↑) and Down (↓) keys can be mapped for recalling history. Enter key
can be pressed over a previous line to bring it live on the interpreter. The
underscore (_) refers to the last object typed.

>>> 5
5
>>> _ # The _ trick 5
>>> x =_

[ ‘_’ (underscore) to refer last typed.]


>>> print(x)
5

1.6 Built-in Functions


To call the function-names from (__builtins__) module requires no
reference.
# Numbers: integer, float, complex

>>> float(23) # Decimal fraction 23.0


>>> int(23.58) # The integer part 23
>>> complex(2) # Complex number (2+0j)
>>> complex(2, 3)
(2+3j)

# Absolute values

>>> abs(-23.56) # Absolute value 23.56


>>> int(abs(-23.56))

23
[Integer part of absolute value] >>> abs(3 + 4j)
5.0
[Absolute value: √32+ 42]
# Power >>> pow(2, 4) # 24 16
# Minimum, Maximum

>>> min(0, -2, 1, 10)


-2
[Smallest of the arguments]
>>> max(0, -2, 1, 10) 10
[Largest of the arguments]

# Rounding off

>>> round(13.145) 13 [Rounding off to nearest integer] >>> round(23.9)


24

>>> round(23.9237, 2) 23.92


[Round off to 2 decimal digits]

>>> round(23.9237, 3)
23.924
[Round off to 3 decimal digits]

>>> round(1/3, 3) 0.333

Note: If nothing is specified, round()function rounds off the decimal number


to the nearest integer (ceiling or floor of the number).

The round off rule: The last decimal digit ( ��) till which it is rounded off,
is increased by 1 when (�� + 1)-th digit is >= 5, else it stays the same.

The round off anomaly:


>>> round(2.685, 2)
2.69 # The output as expected >>> round(2.675, 2)
2.67 # Anomaly

The last output is 2.67, but it is expected to be 2.68 since the third decimal
digit in 2.675 is 5. This anomaly occurs due to decimal number
representation error. To look at the actual representations of the numbers
2.685 and 2.675 by the processor, we import a function, Decimal() from the
module decimal.

Decimal number representation:


>>> from decimal import Decimal >>> Decimal(2.685)
Decimal('2.68500000000000005329070 5182007513940334320068359375')
>>> Decimal(2.675)
Decimal('2.67499999999999982236431 605997495353221893310546875')

In the decimal number representation of 2.675 in computer, the third digit


after decimal point is 4 and not 5. Hence is the resulting rounding off
anomaly. We must take note of such rounding off or approximations when
we consider numbers with high precision.
Anomaly removed:

>>> round(Decimal('2.675'), 2) Decimal('2.68') # Anomaly gone

[ Note: Sometimes, one exception to the usual round off rule is considered. If
the digit next to the round off place is 5 which is followed either by 0 or
nothing (for example, in 2.685 or 2.675) then 1 is not added to digit at the
round off place. For more on this, see Chapter 0, page 14.]

# Creation of Numbers
We can create a collection of integers by a built-in functionrange().

>>> x = range(1, 10) >>> type(x)


<class 'range'>

In the above, we created x which is an object called ‘range’. It is a collection


of integers:1, 2, 3, … 9. We can convert this collection to other special types
as follows.

# Convert to other types

>>> list(x)
[1, 2, 3, 4, 5, 6, 7, 8, 9] >>> tuple(x)
(1, 2, 3, 4, 5, 6, 7, 8, 9) >>> set(x)
{1, 2, 3, 4, 5, 6, 7, 8, 9}
We have the following data structures (types of data collections): list(),
tuple(), set(). These functions, in general, are called iterables. This means,
they are composed of elements. We can extract the elements when we iterate
over them in a loop. [More on this in chapter 2.]

# Iterate and get the numbers.

>>> for i in range(1, 5): print(i, end = ‘ ’)


1234

Note:
A loop is an iterator. The above is a typical syntax of writing a for-loop. The
loop starts with keyword for, followed by some dummy index ‘i’ and then
you put some iterable which ends with a colon. Under this, there is one
indented statement to print. [We demonstrate more on this in the next
chapter.]

1.7 Numbers in Different Bases

# Conversions
>>> bin(12) # Decimal to binary '0b1100'
>>> 0b1100 # Binary to Decimal 12
>>> oct(48) # Decimal to Octal '0o60'
>>> 0o60 # Octal to Decimal 48
>>> hex(31) # Decimal to Hexa '0x1f'
>>> 0x1f # Hexa to Decimal 31

Number System prefix


Binary ‘0b’ or ‘0B’ Octal ‘0o’ or ‘0O’ Hexadecimal ‘0x’, or ‘0X’

1.8 Input and Output (I/O):


1.8.1 Input Styles

x = 15
y = 12.6
z = -10
x, y, z = 15, 12.6, -10 [Multiple inputs in one line] a = input(‘Enter value’)
[For prompting]
a, b, c = input('Enter values \t') # \t for space a, b, c = input(‘Enter values

\ n’) [\n for new line]


name = ’abhi’

Note:
• Alphabetic characters are put inside quotes. Anything surrounded by quotes
is a string. In the above, ‘abhi’ is a string.

• Also note that the input values taken through the input() function will return
as strings. We will discuss about that in detail later.
• Recognize that any name followed by parenthesis () is usually a function.
Here, input() is a built-in function. In Python, we come across many such
built-in functions.

>>> a = 100
[The number 100 assigned to ‘a’.] >>> print(a)
100
>>> a, b, c = 2, 3, 5
[Multiple assignments on one line] >>> a
2
>>> b

3
>>> x, y = 10, 'My name' [A Number, a String] >>> x
10
>>> y
'My name'

1.8.2 Output Styles


The syntax:
print(values, sep = ‘ ’, end =

‘ \n’) It prints values with gap (if no separation is mentioned and the cursor
stops on the new line due to ‘\n’ as default.)

x, y = 2, 3
print(x, y) Output: 2 3
print('values = ', x, y, sep = ',') Output: values = 2,3
print(x, y, sep = ',', end = ':') Output: 2,3:
Note:In Python 2, we write ‘print x, y’, where the print is a command and not
a function.

1.8.3 Input from Outside


Many often we take input data not directly inside a written code (Python file)
but from outside as we run the code.

>>> a = input()
∎ ← Cursor waits here for input

# Prompt string inside >>> a = input(‘Enter value’)


Enter value ∎ ← The cursor waits

at the end. >>> a = input(‘Enter value \n’) Enter value

∎ ← Cursor waits here; ‘\n’ creates next line


>>> a = input('Enter value \n')
Enter value

10 ← Value entered. >>> a


'10' ← Input is a string now. >>> type(a)
<class 'str'>

Note: The value entered through the built-in functioninput()is astring. In fact,
in Python 3, every input entered through the input() function is a string. [The
Python strings are unchangeable. This is an important consideration for input
as data strings.] In Python 2, a number entered through input() function
remains a number only (not a string).

1.8.4 Evaluate the Input String

The built-in function eval() is used to evaluate the input string. Thus, we get
back the number from a string of number.

>>> a = input() # Data input 10


>>> a
'10' # Output as string >>> a = eval(a) # Evaluate the string >>> a
10 # Output is a number >>> a = eval(input()) # Directly 10
>>> a
10

# For multiple inputs

>>> x, y, z = eval(input('Enter the values \n'))


Enter the values
2, -1, 0
>>> x
2
>>> y
-1
>>> z
0

Note: The prompt string, ‘Enter the values \n’ is to display the line: ‘Enter
the values’. The ‘\n’ that follows the string, is to create a new line in the
display.

1.8.5 Formatted Output

Formatting is to write output strings/ data in predefined styles or formats. We


can give any number of spaces in between two elements, create new lines etc.
as we represent the output data in a way we wish. The formatting is done
with strings and so it is called string formatting.

>>> x, y = 4, 5
>>> print(x, y) # Unformatted 4 5

# Positions: 0 & 1
print('{0} and {1}'.format(x, y)) Output: 4 and 5

# Positions altered
print('{1} and {0}'.format(x, y)) Output: 5 and 4

Print the following floats with string format style.


pi = 3.141592653589793 e = 2.718281828459045

# Fixing decimal places by f >>> print('{:.3f}'.format(pi)) 3.142

>>> print('{0:.4f},
{1:.5f}'.format(pi, e))
3.1416, 2.71828
>>> print ('{1:.4f},
{0:.5f}'.format(pi, e))
2.7183, 3.14159

>>> x, y, z = 35.8679, 12.6354, 123.7823


>>> print ('{0:.2f}, {1:.2f}, {2:0.2f}'.format(x, y, z))
35.87, 12.64, 123.78
>>> print ('{2:.0f}, {1:.2f}, {0:0.1f}'.format(x, y, z))
124, 12.64, 35.9
# Express a percentage

>>> x = 0.0158976
>>> 'Approx value =

{:.2%}'.format(x) 'Approx value = 1.59%'


>>> 'Approx value =

{:.2%}'.format(x) 'Approx value = 1.58%'


# Output in Exponential form

>>> a = 805682.47
>>> '{:.4E}'.format(a)
'8.0568E+05'

>>> '{:.2E}'.format(a)
'8.06E+05'

1.9 Writing Codes


On Interpreter

Let us write Python programs of multiple lines (We call it a ‘script’ when we
write in a file.) on Python interpreter. [As we write a line on interpreter and
press Enter key, the line or instruction will be interpreted. Then the
interpreter (command line) will be ready to take next instruction. If there is
something wrong in syntax or some errors, it will return some error message.
We need not compile the entire script at a time to check.]

# Evaluating expressions

>>> x, y = 2, 3
>>> z = x*y - x/y
>>> print(z)
5.333333333333333

Note: Division of integer by integer in Python 3 returns a float whereas the


same returns an integer (the quotient in division) in Python 2 (and in other
computer languages). In Python 2, we need to take at least any of numerator
and denominator as float. So, in Python 2, we must write 5.0/3 or5/3.0
orfloat(5)/3 etc. ]

>>> a, b, c = 2, 3, 5 >>> x = b**2 - 4*a*c >>> print(‘x = ‘, x)

x = -31 [output]

Note: The string, ‘x = ‘, inside print(), is to appear in the output. Two


arguments (the string and the variable name) in the print() function, are
written with comma separated.

>>> v0, g, t = 10, 9.81, 0.1 >>> h = v0*t - 0.5*g*t**2


>>> print(round(h, 2))
0.95 # Up to 2 decimal points

Writing in a File

To write a Python code in a file (often called Python script), one can write the
lines of instructions in a file (by clicking the ‘file’ option on interpreter
window or by creating a plain text file with any Windows or Linux editor.).
The file (named assomething.py) is then executed by clicking ‘run’ option on
interpreter window or by writing ‘python something.py’ on Linux/ DOS
command line. The demonstration on IDLE interpreter is as follows:

1.10 Read/ Write data


Open a file to read or to write data from within a Python script:

f1 = open(‘filename’, ‘w’) f2 = open(‘filename’, ‘r’) [’w’ = writing, ’r’ =


reading]

Other options

• ‘w’→ Opens a file for writing only. Overwrites the file if it exits, else it
creates a new file.
• ‘w+’→ Opens a file for both reading and writing. Overwrites the file if it
exists. If the file does not exist, it creates new file for reading and writing.
• ‘r’→ Opens a file for reading only. The file pointer will be placed at the
beginning of the file.
• ‘r+’→ Opens a file for both reading and writing. The pointer is placed at
the beginning of the file.
• ‘a’→ Opens a file for appending. So, it adds lines or data at the end of the
previously existed lines. If the file does not exist, it creates a new file for
writing.

Write in a file:
print(x, y, f1)
[Write x, y in file, indexed ‘f1’]
Close a file:
f1.close()
Note:

We can refer the files with some index/ identifier. For example, we wrote
‘f1’, ‘f2’. We can give any name. In place of ‘filename’, as argument in
open(), we may write any name, with or without extension. However, it is a
good practice to write filename with extension, such my_file.text etc. This
maintaining file systems.
as test.dat, is helpful in

1.11 Commenting

As we write standard Python scripts, we take care of the readability to others


and, we make it easier for our own reading later. For this purpose, we write
documentation, title etc. inside a script.

1.11.1 Comment a Single Line


We can comment a line if we put a # (hash) symbol in front of line. This
means, the compiler will ignore this line, yet we can keep this in a code.

Example:

# This is to compute the sum of numbers n = 100


sum = 0
for i in range(n): # This is a loop sum = sum + i
print(‘sum = ‘, sum) # Print result

1.11.2 Comment a Block of Lines


Also, we can comment a block of text or code with triple quotes. (On IDLE
interpreter or Python shells, this usually makes a change of text color of that
block, to be identified easily.)

“““
This part is for documentation. The following code is to solve a differential
equation by Euler Method.

CHAPTER

2
Built-in Modules
“Tell me and I forget. Teach me and I remember. Involve me and I learn.” -
Benjamin Franklin
2.1 What is a Module?

A module is a Python code consisting of functions, classes and data


structures, statements etc. In short, a module is an organized python code. We
can think of a module as a library from where we can import specific
functions and classes for our work. In fact, a simple written code by us (may
be with a user defined function in it) can be treated as a module.

We can import a module on our namespace by executing import statement:


‘import module_name’, where the module is a python file:module_name.py.
For every Python script or source file, the extension‘.py’ is a default one.

Once we import a module on the interpreter (or Python shell), we can go on


calling functions and other modules from it. Only when we come out of the
interpreter (or shell) window and open a new session, we need to import
again as the new namespace will be created.

As it was mentioned earlier, core Python has several modules for various
purposes apart from the built-in module (__buitins__) that we have already
seen. Below, we will make use ofmathand cmath (mathematical modules for
real and complex numbers) and other modules to write some simple codes.
2.2 Import Module
>>> import sys # Import module >>> dir(sys) # Check directory

# Obtain help on specific item >>> help(sys.stdin)


………
>>> sys.version
'3.8.3 (tags/v3.8.3:6f8c832, May 13 2020, 22:37:02) [MSC v.1924 64 bit
(AMD64)]'

Note: The standard technique to import a module is to type import followed


by module-name. In the above, sys is a module that is imported. Then the
methods and attributes in it are referred by connecting with a dot. The sys
module provides us with system information.

The built-in function dir()is to display what the module contains. [A module
can display strings, parameters, functions, and classes etc. that are in it.] To
check any of them and to obtain help, we type another built-in function
help(). [This provides us with online manual to read which is always helpful.]
The last line, sys.version returns a string that provides the information about
the version etc.

Let us talk about the verse: ‘The Zen of Python’ which captures the essential
philosophy of Python Computing. We can obtain the poem with the
following import.

>>> import this


The Zen of Python, by Tim Peters

Beautiful is better than ugly. Explicit is better than implicit. Simple is better
than complex. Complex is better than complicated. Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity. Errors should never pass silently. Unless
explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea. Namespaces
are one honking great idea
-- let's do more of those!

# All the modules in core Python

>>> help(‘modules’)
……
Please wait a moment while I gather a list of all available modules...

[Note: The output returns a huge list which may take some time to appear.]
# Import the module >>> import module
# Check what are there >>> dir(module)
# Help on some name in module >>> help(module.name)
2.3 Math module

In math module, there is a collection of many built-in mathematical functions


- trigonometric, exponential, logarithmic etc. and some mathematical
constants (all based on real numbers).

# Import module and check

>>> import math


>>> dir(math)
['__doc__', '__loader__', '__name__', '__package__', '__spec__', 'acos',
'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'ceil', 'comb', 'copysign', 'cos',
'cosh', 'degrees', 'dist', 'e', 'erf', 'erfc', 'exp', 'expm1', 'fabs', 'factorial', 'floor',
'fmod', 'frexp', 'fsum', 'gamma', 'gcd', 'hypot', 'inf', 'isclose', 'isfinite', 'isinf',
'isnan', 'isqrt', 'ldexp', 'lgamma', 'log', 'log10', 'log1p', 'log2', 'modf', 'nan',
'perm', 'pi', 'pow', 'prod', 'radians', 'remainder', 'sin', 'sinh', 'sqrt', 'tan', 'tanh',
'tau', 'trunc']

# Online help over sin() function >>> help(math.sin)


# Some functions from math module
>>> math.sqrt(100)
10
>>> math.pow(2, 4) # 24 16.0
>>> math.cos(2)
[Argument is treated as radian.]
-0.4161468365471424
>>> math.exp(-2)
0.1353352832366127
# Factorial of a number
>>> math.factorial(100)
9332621544394415268169923885626670
0490715968264381621468592963895217
5999932299156089414639761565182862
5369792082722375825118521091686400 0000000000000000000000

Note: Earlier we used pow() function which was taken from the built-in
module (__builtins__). In that case, we did not have to refer the module name
as it was a built-in function. We directly wrote: pow(2, 4).

# More functions from math module # gcd = greatest common divisor

>>> math.gcd(18, 4)
2
>>> math.log(e)
1.0
# Radian to degree conversion >>> math.degrees(pi)
180.0
>>> math.degrees(1.571)

90.01166961505233

# Degree to radian >>> math.radians(180) 3.141592653589793 >>>


math.radians(45) 0.7853981633974483

# Hypotenuse
>>> math.hypot(3, 4) 5.0

# Fractional and integer parts >>> math.modf(12.5)


(0.5, 12.0)

Application: Stirling’s formula is to find out approximate value of logarithm


of the factorial of a large number:ln ��! ≈ �� ln �� − ��. To verify
this formula numerically, we use, ln ��! =∑ ln ������=1 . In fact, we
do not have to compute factorial for this problem!

Python Script:
# To test Stirling’s approximation

import math
n = eval(input('Enter Number \n')) lognfac = sum([math.log(i) for i in

range(1, n+1)]) approx = n*math.log(n) - n


prop = (lognfac - approx)/lognfac

#Proportional error
print (‘Approx value, Exact value, Percentage Error’) print (approx,
lognfac, prop*100)
# Constants from math module

>>> math.e
2.718281828459045
>>> math.pi
3.141592653589793
>>> math.cos(math.pi/4) # cos(��/4)

# Other styles of import # Rename the module >>> import math as m

# Call by local name


>>> m.sin(2) >>> m.pi
3.141592653589793

# Import specific names


>>> from math import sin, pi

# No reference required >>> sin(pi/4)


0.7071067811865475
# Import everything
>>> from math import *
# No reference required >>> sin(pi/4)
# Wrong way
>>> import os, sys, math

Note: More than one module cannot be called in one line. Interested readers
can consult PEP 8 – Style guide for Python code.

Applications:
#1 Area of a Circle

import math
r=5
area = math.pi*r**2 print('Area = ', area)

Note: In Python 3, input data taken through input() is a string. So, we need to
evaluate the string into a number by eval().

#2 Value of sin ��
import math
theta = 45 # Angle in degree
# Convert to radian
theta = theta*180/math.pi print(math.sin(theta))
# 3 Area of by Heron’s Formula

Given, two sides of a triangle: �� and �� and the angle between


them:��, we calculate the third side, �� = √��2+ ��2− 2����
cos ��. The area of the triangle, �� =√��(�� − ��)(�� − ��)
(�� − ��), where �� = (�� + �� + ��)/2.

Algorithm:
1. Input: ��, �� and �� (in deg.)

2. Covert the angle �� into radian


3. Find the third side �� from formula.
4. Calculate s.
5. Calculate area from formula and print output.
# Python Script: Area of triangle by Heron's formula
from math import pi, sqrt, cos

a, b, theta = eval(input('Enter a, b, angle (in degree): \n'))


# Convert into radian
theta = pi*theta/180
# Third side
c = sqrt(a*a + b*b –
2*a*b*cos(theta))
# Calculate s
s = (a + b + c)*0.5
# Calculate area
area = sqrt(s*(s - a)*(s - b)*(s – c)) print('Area = ', area)
Note: The functions and parameters: cos(), sqrt(), pi are imported from math
module.
2.4 Complex Math Module >>> import cmath
>>> dir(cmath)

['__doc__', '__loader__', '__name__', '__package__', '__spec__', 'acos',


'acosh', 'asin', 'asinh', 'atan', 'atanh', 'cos', 'cosh', 'e', 'exp', 'inf', 'infj', 'isclose',
'isfinite', 'isinf', 'isnan', 'log', 'log10', 'nan', 'nanj', 'phase', 'pi', 'polar', 'rect',
'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'tau']

Note: The arguments of mathematical functions in cmath can be real or


complex. But the outputs are always returned in complex form.

>>> cmath.sqrt(-1)
1j
>>> cmath.sin(2)
(0.9092974268256817-0j)
>>> cmath.exp(2 + 3j)
(-7.3151100..+1.0427436562359045j)

# Phase angle
>>> cmath.phase(2 + 3j) 0.982793723247329

Note: Imaginary number(�� =√−1) is 1j. We can write 0j, 1j, 1.5j, -5.96j
etc. but without any prefix Python understands j as a variable name (some
number to be prefixed).

>>> 0j
0j
>>> 1j
1j
>>> j
Traceback (most recent call last):
File "<pyshell#137>", line 1, in <module>

j
NameError: name 'j' is not defined

List of Mathematical functions

Some mathematical functions in math andcmath modules, which are


frequently used for scientific programming,

• sqrt(x)→ √��
Examples: math.sqrt(5), cmath.sqrt(1+2j)

• factorial(n)→ ��! = �� ×(�� − 1)× … × 3 × 2 × 1


• ceil(x)→ Ceiling: smallest integer > = ��.
Example:ceil(5.3) = 6
• floor(x)→ Floor: largest integer <= ��:
Example: floor(5.3) = 5
• exp(x)→ Exponential function:

����
Examples: math.exp(3), cmath.exp(2+1j)

• log(x), log10(x)→ Natural logarithm, log (base 10)


Examples: math.log(2), cmath.log(1+2j)

• sin(x), cos(x), tan(x), cot(x) Trigonometric functions, arguments in radian.

Examples: math.sin(2), cmath.sin(2+1j)


• asin(x), acos(x), atan(x), acot(x)
Inverse functions

• sinh(), tanh() ← Hyperbolic functions


• pow(x, y)→ ����

2.5 Module for Random Numbers

Random numbers are those which appear randomly and have no correlations
among them. We come across random numbers in Nature. In electrical noise,
in atomic vibrations or in Brownian motions of molecules, we get random
variations in some quantities that we measure. They do not follow any
definite pattern, or we can say, there is no mathematical law that can bind the
sequence of numbers. The occurrences of random numbers may follow a
certain distribution, but they appear randomly. In the study of many natural
phenomena and in computer modeling of them, the random numbers play
important roles. So, we need to access a set of random numbers for our study.
Moreover, in the subject of Cryptography, random numbers play a big role.

How to generate random numbers in Computer? In Computer, we generate


pseudo random numbers by employing the system’s inherent mechanism of
number handling. We call them pseudo random as the numbers are random in
some range, a very long range in fact, but out of the range they will repeat.

# Import the Module

>>> import random >>> dir(random) ['BPF', 'LOG4', 'RECIP_BPF',


'SG_MAGICCONST', 'TWOPI', '_Sequence', '_Set', '__all__', '__builtins__',
'__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__',
'__spec__', '_accumulate', '_acos', '_bisect', '_ceil', '_cos', '_e', '_exp', '_inst',
'_log', '_os', '_pi', '_random', '_repeat', '_sha512', 'NV_MAGICCONST',

'Random', 'SystemRandom', '_sin', '_sqrt', '_test', '_test_generator',


'_urandom', '_warn', 'betavariate', 'choice', 'choices',
'gammavariate', 'getrandbits', 'lognormvariate', 'paretovariate', 'randint',
'random', 'randrange', 'sample', 'seed', 'setstate', 'shuffle', 'triangular', 'uniform',
'vonmisesvariate', 'weibullvariate']
'expovariate',
'gauss', 'getstate', 'normalvariate',

We see a list of various random number generators or functions. We can


further check the online manual of each of the random number generators as
usual. For example,

>>> help(random.random)
Help on built-in function random: random() method of random.Random
instance
random() -> x in the interval [0, 1).

2.5.1 Generate Random Numbers

The function, random() in the module, random produces uniform random


fraction between 0 and 1 (the upper limit 1 not include). Note the interval: [0,
1)

>>> random.random() 0.5273383762699028 >>> random.random()


0.10447680244370472

Note: Two different random numbers are generated by calling the same
function twice.

# Create a list of 10 random numbers in [0, 1)


>>> [random.random() for i in

range(10)] [0.5350878140843952,
0.43405195589115175,
0.3065609996673083,
0.12325970038943124,
0.7195962696461913,
0.06200758778693172, 0.6430032231626809, 0.4271828905197539,
0.26735110979806964, 0.41317992315825525]

Note: The way we generated a list of 10 random numbers, is called list


comprehension. We shall learn more about this later in Chapter 3.

# Fix a seed value, get a certain sequence


# Seed value can be any integer. >>> random.seed(234)
>>> random.random()
0.3418531586584459
>>> random.random()
0.8655952517507371

# Put the same seed, get the same sequence


# Same Seed
>>> random.seed(234)

# Same random number as before >>> random.random()


0.3418531586584459

# Different seed, get different sequence


# Different seed >>> random.seed(547)

# Different random number >>> random.random()


0.04534988161204301

# Random integer between [a, b]

# Includes both the limits >>> random.randint(2, 10) 6


>>> random.randint(2, 10) 9
# Random integer between [a, b)

# Excludes upper limit


>>> random.randrange(3, 25) 9

# Uniform random fraction between limits


>>> random.uniform(4, 10) 6.539806620593668

Note: uniform(a, b) generates random numbers in range [a, b) or[a, b]


depending on rounding.

# Random number from Normal distribution


>>> random.normalvariate(1, 2) 0.8681258604003476

Note:normalvariate(mu, sigma)
generates random number from Normal distribution with given mu (mean)
and sigma (standard deviation).

2.5.2 Random Choice

We may have a predesigned collection of numbers or items. It is sometimes


necessary to choose random items from that.

>>> L = range(10)
>>> random.choice(L)
4
>>> random.choice(L)
7
>>> x = [‘Apple’, ‘Banana’,

‘ Grapes’, ‘Mango’] >>> random.choice(x)


‘Banana’
>>> random.choice(x)
‘Apple’

Application:
Let us imagine a Random Walk on a plane.

We can consider a two-dimensional square grid or a graph paper. Someone or


some particle may move over the grid randomly from one node to another. At
one unit time step, the subject moves one-unit length in either of the
directions: East (positive xaxis), West (negative x-axis), North (positive
yaxis), South (negative y-axis). We can easily generate a sequence of moves
with the application ofrandom.choice().

The change in position coordinate (x, y):


�� → �� + ∆��, �� → �� + ∆��
The steps along x-axis:∆�� = 0, 1, −1 and the same is true for y-axis.
[∆�� = 0 indicates no increment in x-direction, ∆�� = 1 for the one-unit
increment in the positive x-direction and∆�� = −1 for one-unit increment in
the negative x-direction.]

The choice is out of the following 4 pairs: ∆��, ∆�� = [(��, ��),
(−��, ��), (��, ��), (��, −��)]

# Python Script: Random Walk in 2D # Initial position x, y = 0, 0

# Two empty lists X, Y = [], []


for i in range(1000):
dx, dy = random.choice([(��, ��),

(−��, ��), (��, ��), (��, −��)] ) x = x + dx # Update x y = y +


dy # Update y X.append(x) # Append to list Y.append(y)

# For plotting
import matplotlib.pyplot as plt plt.plot(X, Y)
plt.show()
Fig. 1 A Random Walk on a square grid generated through Python code.
2.5.3 Random Sampling

In statistical estimate over some field survey or so, we need to consider


random samples out of the population. We can fix the sample size and collect
random samples.

# Population: Numbers 0 to 99 >>> p = range(100)

# Random sample of size 5 >>> random.sample(p, 5) [97, 38, 18, 72, 54]

# Another sample of size 5 >>> random.sample(p, 5) [72, 84, 14, 90, 17]
2.5.4 Random Shuffling

>>> x = [1, 2, 3, 4, 5, 6, 7, 8] >>> random.shuffle(x)


>>> x
[8, 5, 7, 4, 2, 1, 6, 3]
>>> random.shuffle(x)
>>> x
[8, 1, 2, 3, 5, 7, 4, 6]

2.5.5 Cryptographically Secured Random Number

There is system dependent (Machine specific) pseudo random number, used


for data security. The generator returns in ASCII strings.

>>> import os
>>> r = os.urandom(4)
>>> r
b'\xef\xe2\x1fA'
>>> len(r)
4
[ASCII code string of length 4]

# To decode

>>> import struct


>>> struct.unpack('i', r) (1092608751,)
[In integer]

>>> struct.unpack('f', r) (9.992903709411621,) [In float]

# Unpacking index for different lengths

>>> struct.unpack('d',
os.urandom(8))
(7.233012407285722e-05,)

>>> struct.unpack('h',
os.urandom(2))
(-25353,)

2.6 Time, Date, Calendar

In Python, time is measured in units of seconds (floating point numbers) and


the clock ticks the number of seconds since 12.00 A.M., January 1, 1970. To
know time, we need to import the time module. In the following, we used the
time()function to know the present time.

>>> import time >>> time.time() 1593926900.532799 >>> time.time()


1593926909.4718668 >>> time.time() 1593926914.2176676

Time is ever changing, as we can see from the above by repeatedly typing the
same command on interpreter. Thus, we can calculate the difference in time
between two instants. This is very useful for knowing the run time of a
Python program.

If we import the time module inside a python script and keep track of initial
time (execution of first line of the program) and final time (when the program
is over), we can know how long a program took to run. Typically,

import time
begin = time.time()
Instruction Block
end = time.time()
run_time = end – begin
# For local time and Date (in hours, min, sec etc.)

>>> time.localtime(time.time()) time.struct_time(tm_year=2020,


tm_mon=7, tm_mday=5, tm_hour=11, tm_min=5, tm_sec=16, tm_wday=6,
tm_yday=187, tm_isdst=0)

# Calendar

>>> import calendar


>>> cal = calendar.month(2020, 7) >>> print(cal)

July 2020
Mo Tu We Th Fr Sa Su

12345
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31

Note: By default, these calendars have Monday as the first day of the week,
and Sunday as the last (the European convention). Various methods can
create calendar of various styles.

Exercises

1. Check the datatypes of (i) 346.89, (ii) 14/5, (iii) 20/3.0, (iv) 2(3+6j), (v)
‘12345’
2. Are the following statements alright? Type and check. Find the errors, if
any. (i) 6x = 6x, (ii) x8 = x8, (iii) x5 = x*5, (iv) x*3 = 3*x
3. Write a Python program to compute ��, where �� = ��(�� −
��)/(�� + ��) with �� = 5, �� = 9. Print the output and check the
datatype of��.
4. Supposewe have three strings, x = ‘Hello friends!’, y = ‘That is why I feel
good.’, z = ‘I learnt Python.’ Can you print the three strings together to have
a meaningful conversation?
5. The real and imaginary parts of a complex number �� are 3.5 and 1.8
respectively. Construct the complex number and find the value of����∗.
6. If��1= 2 + �� and ��2= 3 − 2��, compute |3��1− 4��2|.
7. Take three numbers, integer, float and a complex: 34, -56.98, 5 + 2j. Take
the product of the three numbers and find out the type of the output.
8. Prove the following identity numerically for any angle:cos5�� =
16cos5�� − 20cos3�� + 5cos��
9. Given, the Cartesian coordinates of two points: ��1= (2, 3, 5) and
��2= (9, 0, −1). Write a Python code to compute the distance between the
two points.
10.Import a module ‘sys’ on your namespace. Check the directory and find
out a possible function there in the directory to check your Python version.
11. Print the calendar of March 2021.
12. Convert the decimal number 5983 into a (i) binary, (ii) hexadecimal, and
(ii) octal number.
13. Use the built-in function to find the maximum and minimum among the
following numbers: −1, 2, 10, 13, 3.5, 71.9, 23, −34, 0, 0.98, 11.3, 10
14. Create 5 random numbers from a Normal distribution with �� = 0.5,
�� = 1.
15. Create 3 random fractions between 2 and 3 and find out the mean of those
numbers.
16. Create all the odd integers between 3 and 17.
17. Write a Python program to write ‘area = 50’ with 2 spaces each before
and after the equality symbol.
18. Accept three numbers as user input and then find the mean of them.
19. Print0.000053 in exponential format.

20. Evaluate the Golden ratio √5+1 in decimal2

fraction and print that up to 5 decimal points. 21. Import the special
numbers�� and �� frommath
module. Round off each number up to 3
decimal points and then find the value of����2. 22. Check the famous
Euler identity:������= −1.
[Take �� frommath module.]
23. There is a module called, ‘platform’ in core
Python. Import this module and use a function
system() from it to check which operating
system you are using.
24. A square is inscribed inside a circle of radius 5
unit. Find the ratio of the areas between the
circle and square.
25. Consider the following 10 numbers:0.2, 0.4,
0.9, 0.1, −0.3, −0.4, 0.6, 0.4, −0.5, −0.1.
Take a random sample of 5 numbers from this
and find the maximum out them.
26. Verify:ln 100! ≈ 100 ln 100 − 100.

27. Compute (����) =��! for �� = 7, �� = 3��!(��−��)!


[Use function frommath module.]
28. Check the trigonometric identity: tan(�� + ��)= tan ��+tan ��
for�� = 42�� and �� = 340. 1−tan �� tan ��
29. Check that for any real numbers �� and ��, ��2���� cot
−1��(����+1)��= 1.
����−1

30. Write a Python script which accepts the first and last name of the user and
print them in reverse order with a gap between them.

31. Write a Python script to print the calendar of March’19. [Use calendar
module.]
32. If��1= 4 − 3�� and ��2= −1 + 2��, evaluate: (a) |��1+ ��2|,
(b)��1∗+ ��2∗ and find out the real and imaginary components in each
case.
33. Print the The Zen of Python poem.

CHAPTER

3
Loops and Conditions
“Logic will get you from A to B. Imagination will take you everywhere.” -
Albert Einstein

To write a long and organized code following some algorithm, we need to


learn how to write blocks of instructions with loops and logical structures.

Note: If the Python program is more than a few lines, it is advantageous to


write in a file and store. We call this a Python script. Even if we write a code
in a file, the command line or interpreter (>>>) may always be readily useful
where we can quickly test each line of instruction.

3.1 Loops
3.1.1 For-Loop

To compute something repeatedly, we put an instruction or a block of


instructions inside a loop. In Python, it is a ‘forloop’. The loop runs over
some iterable. That means, we need an iterable over which the loop will run
repeatedly. [We can think of an iterable as a collection of items/ elements.]

# Structure of for-loop

for i in range(start, end, step): instruction 1


instruction 2
……
print(…)

Note on Syntax:

• The loop starts with the keyword,‘for’ followed by some dummy index (‘i’,
in this case) and then another keyword, ‘in’ which is followed by an iterable.
• In this case,range() is an iterable. The loop iterates over it. This is followed
by a colon (:) which is to connect the next lines of instructions.
• The instructions must beindented under the loop. Also, all the indented
instructions are to begin from the same column.
Fig. 1: Concept of indentation: how the nested blocks are indented.
range()

The object range() is as an iterable. Some other iterables are:string, list, tuple,
set, dict.

The built-in function, range(start, stop, step)creates a range of integers from a


value ‘start’, in the steps of‘step’ and stops before the ‘stop’ (Not to touch or
cross the end point). The range object can be converted into other kinds of
iterable objects.

# Conversion
>>> range(1, 10, 2) range(1, 10, 2)

# Convert to a list
>>> list(range(1, 10, 2)) [1, 3, 5, 7, 9]

# stop = 5, start = 0, step = 1 >>> range(5)


range(0, 5)
# Default start = 0, step = 1 >>> list(range(5))
[0, 1, 2, 3, 4]

# Convert to a tuple
>>> tuple(range(2, 10, 3)) (2, 5, 8)

Note: In Python 2, range() creates alist of integers directly and xrange()is


equivalent to range() in Pythion 3.

[In Python, an iterable is an object which can be iterated. The basic


mechanism is that it generates an iterator when passed to iter() method.
Iterator is an object, which is used to iterate over an iterable object using
__next__() method that returns the next item of the object. Whenever we use
a for-loop, the next method is called automatically to get each item from the
iterator thus going through the operation.]

Python code #1
for n in range(1, 4): print(‘Very good’)
Output:

Very good Very good Very good

Note: The function, range(1, 4) generates integers from 1 to 3. So, the for-
loop runs forn = 1, 2, 3 [The index n is a dummy index. We can write
anything.].

Python code #2
for x in [0, 1, 2]: print(x, "\t", 2**x)

Output:
01

12
24

Note: In the print() function, “\t” (creates one space by tab) is used to
display the output in the gap separated way.

Python code #3
# Sum of integers from 1 to 100 s = 0 # Initial value for n in range(1, 101):

s = s + n # Update the value


print(‘Sum = ’, s)
Output:
Sum = 5050

# Sum in one step


>>> sum(range(1, 101)) 5050

Python code #4: Number Table (Print with string formatting)


for index in range(1, 6):
print("{} times 5 is
{}".format(index, index*5))
Output:

1 times 5 is 5
2 times 5 is 10
3 times 5 is 15
4 times 5 is 20
5 times 5 is 25

Note: Loops can also be written with other kinds of iterables like tuple, string
etc. in the same way. For example,

>>> for i in 'ABC':


print(i, end = ‘ ’)
ABC

Factorial of a Number ��! = 1 × 2 × 3 × … .× ��


Python Script
# Calculate factorial
n = eval(input(‘Enter the number

\ n’)) fac = 1
for i in range(2, n+1):
fac = fac*i
print (‘Factorial = ‘, fac)
Exercises:

1. Calculate the sum of all odd numbers between 1 and 1000.


2. Calculate the sum of squares of all the even numbers between 10 and 500.

Nestedfor-loop:
A nested loop is a loop inside a loop. We can have any number of loop-
blocks inside a loop.

for n in range(5, 10): x = 0


for i in range(n):

x=x+i
print(n, x)

Output:
5 10
6 15
7 21
8 28
9 36

Notes on abbreviations:
The sum and update: ‘s = s + n’ can be abbreviated ass += n. Such
abbreviations can also be applied for product(*), subtraction(-)

and division (/). The short-hand mathematical symbols, +=, -=, *=, /= are
often used for iterative computations.

>>> x = 2 >>> x -= 10 >>> x += 3>>> x


>>> x10
5 >>> x /= 2

>>> x *= 4>>> x
>>> x5.0
20
3.1.2 While Loop

The while loop is a loop with logical decision making. The loop continues if
some condition is satisfied.

Structure of While loop:


while CONDITION:
statement
# Example: Python code

x=1
while x < 10:
x += 2
print(x, end = ‘ ’)

Output:
3 5 7 9 11
Note: print(x, end = ‘ ’) prints the result in one row with a gap after each
element.
# Example: Infinite Loop x = 10
while True:
print(x)

Note: ‘while True’ means loop forever. The while statement takes an
expression and executes the loop body while the expression evaluates to
(Boolean) "true". True always evaluates to Boolean "true" and thus executes
the loop body indefinitely.

3.2 Conditions

The if statement is used for logical decision making with one or more
conditions and instructions under it.

# Logical if
if x == 10:
print(‘x is 10’)
# On a single line
if x == 10: print(‘x is 10’)
Applications:
# Perfect Square Number

Can we write a python code to check if a number is a perfect square number


or not? For example, 25 = 52 is a square number.

[One way is to calculate the square root which will be an integer when perfect
square (√25 = 5) and a float for a non-perfect square (√26 =
5.0990195135927845). In case of a float (and not an integer), the integer part
can be squared again and then to compare with the original number.]

nsqrt = int(sqrt(number))
nsq = nsqrt*nsqrt
if nsq == number: print(‘perfect

square’)
# if...else [Looking for an alternative decision.]

if x == 10:
print(‘x is 10’)
else:
print(‘x is not 10’)

Application:
# Odd or Even

# Python Script: Test a number n = 84824727


if n%2 == 0:

print(‘Even’)
else:
print(‘Odd’)

# Armstrong Number

n = input(‘Enter the number \n’) x = sum([eval(i)**3 for i in n]) if x == n:

print(‘Armstrong Number’) else:


print(‘Not Armstrong’)

Exercise:

Write a Python code to check if a given number is an Armstrong number or


not. You can define a function, say arms() for this. Now use this function to
find out how many Armstrong numbers are there up to 1000000. [You will be
surprised to know that there are not too many.]

# if...elif...else
[Decision with more than one conditions]

a, b, c = 2, 3, 4
x = b*b - 4*a*c
if x < 0:

print('x is negative.') elif x > 0:


print('x is positive.') else:
print('x is zero.')

# Nested if
[When one logicalif is embedded inside another
logicalif.]

if n < 0:
print(‘negative’)
else:
print(‘positive’)
if n > 10:
print(‘More than 10’)

# Multiple conditions
Take the list, x = [1, -1, 0, 9, 3, 4, 5, 6, 7, 11, 100, -3, -5].

for i in x:
if i < 10 and i > 0: print(i, end = ' ') Output: 1 9 3 4 5 6 7
for i in x:
if i > 10 or i < 0: print(i, end = ' ') Output: -1 11 100 -3 -5
Break inside a loop

The keyword, ‘break’ is a control statement to be put inside a loop to break


away when the condition is fulfilled.

# Example: With ‘break’


for i in [10, 16, 18, 21, 29]: if i%2 == 1: # If odd break # Immediate exit
print(i, end = ‘ ’)
print(‘Done!’)

Output:
10 16 18 Done!

3.3 Try Except

• The try block lets us test a block of code for errors.


• The except block lets us to handle the error.
• The finally block allows us to execute the code, irrespective of the result of
the try and except blocks.

try: print(x)
except:
print('Exception Error occurred')

Output:
Exception Error occurred
Note: The error occurred because the argumentx was not initialized.
# With specified error (NameError) try:
print(x)
except NameError:
print('Error: argumemt not defined') except:
print('Unknown Error')
Output:
Error: argument not defined
# Use of ‘else’

try:
print(x)
except:
print('Unknown Error')
else:
print('No Error')

Output:
Unknown Error
# Fix Error

Assign x before the block. It will not now raise error. It will print the value.
x = 10 try:
print(x)

except:
print('Unknown Error')
else:
print('No Error')

Output: 10
No Error

# Use of ‘finally’

This block, if present, will be reached irrespective of the fact that it enters try,
except orelse block.

try: print(x)
except:
print('Error: argument not defined')
else:

print(‘Unknown error’) finally:


print(‘Final block reached')

Output:
Error: argument not defined Final block reached
Exercises
1. Write a Python script that takes the radius of a sphere from user and
compute the volume.
2. Python script to compute the distance between two points:(2, 3, 5) and (−1,
2, 9) .
3. Given some data: �� = 87, 91, 85, 75, 28, 122, 66, 56, find the
(arithmetic) mean and r.m.s. value of the variable ��.
4. Given the following numbers:6, 12, 17, 15, 32, 64, −2, 100, 103, 13, 2, 0.
Find the maximum, minimum and range of the numbers.
5. A set of 20 numbers are given:1, 0.1, 5, 3, 10, −1, 4, 20, 1000, −9, 2, 14,
4.5, 0.9, 30, 9.8, 11, 22, 48, −10 . Write a computer program to count how
many numbers are there between 0 to 10.
6. Write a computer program to compute ��!, where �� = 10.
7. Write a Python script to verify Stirling’s approximation:ln 100! ≈ 100 ln
100 − 100.

8. Test if the following series converges, for sufficiently large numbers:



1
��

9. Consider�� be a complex number:�� = 2 + 3��. Utilize appropriate


functions from a proper module to find out some values of the multiple
valued function �� = ���� ��.

10. Find all the roots for the 5th root of unity. [Hint: 1
1/��
=(������(2������))
1/��
2������

= ���� . Now for a fixed ��, run a for-loop for�� = 0, 1, 2, … (��


− 1) to obtain the roots.]

11. Find the cube roots of�� = ��, where �� =√−1.


12. A set of 20 numbers are given: 1, 0.1, 5, 3, 10,
-1, 4, 20, 1000, -9, 2, 14, 4.5, 0.9, 30, 9.8, 11, 22, 48, -10. Write a program to
count the numbers between 0 to 10.
13. Given some data: �� = 87, 91, 85, 75, 28, 122, 66, 56, find the
(arithmetic) mean and r.m.s. value of the variable ��.
14. Given the recursion relation: ����+1= ����+ ����−1, with
��0, ��1= 0, 1. Write a suitable python script to generate all the values
up to ��15.
15. Importdate() function from the datetime module. Find the number of days
between (2019, 18, 3) and (2019, 29, 3).
16. Write a python script to evaluate Γ(21) where 2

Γ(�� + 1/2)=1.3.5….(2��−1)√�� . 2��

17. Write a Python script to print the alphabet pattern ‘E’ with *.
*****
*
*
*****
*
*
*****

18. Write a computer program for the function ��(��)=(�� − 1)(��


− 2)(�� − 3)(�� − 4)(�� − 5) then evaluate ��(6) from that.

19. Write a Python script to accept any integer (��) and compute:�� +
���� + ������ + ��������
20. Find the value of�� from the infinite series:��=4

1 −1+1−1+1+ ⋯ up to 6th decimal of3 5 7 9


accuracy.

21. Given the recursion relation: ����+1= ����+ ����−1, with


��0= ��1= 1. Write a suitable computer program to generate all the
values up to ��15.

22. Consider the following function: Which function of x is returned by f(x)?


def f(x):

s = 0.0
l = [1,2,3,4]
for c in l:

s = s*x + c
return s

23. Write a function that will take a list of the coefficients ��0, ��1,
��2,….���� as one argument and the value of a variable �� as
another and return the value of the polynomial: ��0+ ����1+
��2��2+ ⋯ + ��������

24. Write a program that will use this function print out a table of the values
of the polynomial: ��3− 7��2+ 5 for the values 0, 1, 2, ... 10 for��.
Why do you think that the code written in this problem has an advantage over
simply returning �� ∗∗ 3 − 7 ∗ �� ∗∗ 2 + 5 (direct calculation)?

CHAPTER

4
Python Function
“Minds are like parachutes– they only function when open.” – Thomas
Dewar

The idea of constructing a function block inside a computer program is for


reusability. Once a function is defined it can be used repeatedly.

We have various system functions in __builtins__ and other modules. In the


following, we show how we can define a function. Such function is called
user defined function. We can define a mathematical (or any kind of)
function inside a Python script.

4.1 Define a Function


def func_name(args):
instructions
return value

A function block begins with the key word ‘def’ followed by the name of the
function with arguments inside the parentheses () and that ends with colon
(:). The instructions (single or multiple lines) are indented under it. The
keyword return is to return the value to be used anytime.

Example: ��(��)= ��2


def f(x): return x*x
# On interpreter >>> def f(x):
return x*x
>>> f(5) 25
# Example: user defined function in

Python Script
# Area of a circle from math import pi

def area(x): return pi*x*x


r = eval(input(‘Enter radius’)) print(‘Area of Circle = ‘, area(r))

Note: The argument in a function can be anything. It may be a variable or


parameter, a string or any other iterable.

# Argument as String
def my_name(name):
return 'My name is ' + name
# Argument as list

def prod(x):
n=1
for i in x:

n = n*i
return n
# Testing on interpreter

>>> x = ‘Abhijit’
>>> my_name(x)
My name is Abhijit
[Two strings added]

>>> x = [1, 2, 3, 4] >>> prod(x)


24

Note: We may also sometimes define a function inside the main block
bracketed by the pair:def main() &main(). Between them, we can place the
indented function block anywhere.

def main():
x = input(‘Enter x’) def f(x):

return x*x
print f(x)
main()
4.1.1 Function with Doc-String

For identification, we may use a doc-string inside a function definition. This


is an optional documentation string (written inside triple quotes) which tells
us what the function is all about. The docstring will not be executed when we
run the function. However, we can get to know the identifier (the doc string)
by the attribute, “__doc__” (double under score on both sides).

For demonstration, we define the following two functions with and without
docstrings. Let us see the difference when we call the identifiers.

# Function without doc-string def f(x):


return x*x
# Function with doc-string
def g(x):

"""This is my defined
function."""
return x**3

# To check the identifier


>>> print(f.__doc__)
None
>>> print(g.__doc__)
'This is my defined function.'

The doc-string gets printed out when we call help() on the function.

>>> help(g)
Help on function f in module __main__:

f(x)
This is my defined function.
The docstring is usually written under triple quotes to accommodate the
string of multiple lines.

• A function begins with def header followed by colon (:) which ends the
header.
• We can write variables and parameters as arguments.
• A docstring is optional.
• The function body can consist single or multiple statements.
• A return statement returns a value.

4.1.2 Void Function


The return statement is a part of a bona fide

function definition. However, it may be noted that some function definitions


do not contain return statement. These functions (without return statement)
are called void, and they return nothing (SometimesNone, Python’s special
keyword for ‘nothing’).

# Example: void function def func(x):


x**2 + 1
>>> func(2) # Returns nothing >>>

In a void function, we can includeprint() to print the output on interpreter.


But it remains a void function only. Withoutreturn statement, it will be
useless to put inside a Python Script.
# A void function with print()
# A true function with return

def void_func(x): print(x**2+1) def true_func(x): return x**2+1

When we call the above functions on interpreter, both returns the same
output. However, when we assign them to some other variables, we see the
difference.

>>> void_func(2) 5
>>> true_func(2) 5

>>> x = 2

# Void function prints the value. >>> g = void_func(x)


5
>>> g # But returns nothing.

>>>
>>> f = true_func(x)
>>> f # The true function returns 5

Note: The assigned object for the void function returns nothing when called
again. However, on interpreter, it prints the value at the time of assignment.
So, a void function with print() may behave like a true function but we have
to be careful when we use that in a Python script.

4.1.3 Some Trivial Functions


A function that returns a fixed value or nothing, can be treated as a trivial
function.
def f(): # Without argument return 2*3
# Returns or prints nothing. def g(x):
pass
# Testing on interpreter

# Returns fixed answer. >>> f()


6
>>> g(2)
>>> # Nothing appears.

4.1.4 Functions with Arbitrary Number of Arguments

Sometimes, we do not know in advance, the number of arguments that will be


passed into a function. Python allows us to handle such situation through
defining the function with arbitrary number of arguments. In the definition,
we use as asterisk (*) before the name of the argument.

def arbit(*x):
for i in x:
print(i**2, end = ‘ ’) >>> arbit(2, 3, 4) 4 9 16

The argument x, in the above function, can take any number of values
(numbers). Let us also try the same with names (strings).

def greet(*names):
for name in names:
return 'Hello' + ' ' + name

>>> greet('Tinku', 'Srabani', 'Monish', ‘Sima’)


Hello Tinku
Hello Srabani
Hello Monish
Hello Sima

4.1.5 Functions with Keyword Arguments

Functions can be called using keyword arguments. When we call functions in


this way, the order (position) of the arguments can be altered as we wish.

# Function with multiple arguments def multi(x, y, z):


return x + y * z
>>> multi(2, 3, 4) 14

We called the function with the values of arguments in place. However, when
we can call by the argument names: x, y, z, they are called keyword
arguments.
# Arguments through keywords >>> multi(x = 2, y = 3, z = 4) 14

# Positions altered, output same >>> multi(y = 3, z = 4, x = 4) 14

4.1.6 Functions with Arbitrary Number of Keyword Arguments

When we do not know how many keyword arguments will be passed into the
function, we add two asterisks (**) before the parameter name in the
argument field. Thekeyword arguments that we pass into a function, will be
placed into a dictionary. We can examine the keys and values of this
dictionary.

def arbit_key(**par): return par

>>> d = arbit_key(a = 1, b = 2, c = 3)
{‘a’: 1, ‘b’: 2, ‘c’: 3}
[Dictionary]

>>> type(d)
<class 'dict'>

Note: Output returned as a dictionary. [A dictionary is a collection of items


with corresponding keys and values.]. In above, the function returned a
dictionary where ‘a’, ‘b’ and ‘c’ are the keys and 1, 2, 3 are the
corresponding values. We can extract the values by putting the keys inside
the square bracket. For example, typing d[‘a’], d[‘b’], d[‘c’] will return 1, 2, 3
respectively.

Some mixed examples


#1 Arbitrary number of arguments and keyword arguments
def test_func(*args, **kwargs): return args, kwargs

>>> test_func(2, 3, 4, a = 5, b = 10)


((2, 3, 4), {'a': 5, 'b': 10})

Note: The *args produce a tuple and **kwargs produces a dictionary.


#2 With default argument and arbitrary keyword arguments

def my_func(x, **par): s = 0


for i in par:

s = s + par[i]*x return s
>>> my_func(5, a = 1, b = 2, c = 3) 30

Note: The above function computes���� + ���� + ����. The


default argumentx is given (without name ref) and the values of the
parameters through keyword argument **par are given asa = 1, b = 2, c = 3.

4.2 Local and Global Parameters

A parameter (or variable) assigned inside a function belongs to the local


scope of that function and can only be used inside that function. We can call
this as local parameter (variable). Change of parameter value from outside
does not affect the function return.

def f(x):
a = 2 # Local parameter return a*x

Even if we change ‘a’ from outside, the function accepts the local value only.
For example,

>>> f(5)
10
>>> a = 10
>>> f(5)
10 ← The value is unchanged.

# Local variable not available outside the function


>>> print(a)
Traceback (most recent call last): File "<pyshell#4>", line 1, in <module>
print(a)
NameError: name 'a' is not defined

A parameter (variable) created in the main body of the Python code is a


global parameter (variable) and belongs to the global scope. Note that global
variables are available from within any scope: global and local.
# Global parameter, put outside a = 5 # Global parameter, outside def f(x):
return a*x

>>> f(2) 10
>>> a = 10 >>> f(2) 20

# Parameter put inside, make it Global.

def g(x): global b b = 4


return b*x

>>> f(5)
20
>>> print(b) 4

Note: We can make a parameter or variable (inside a function) global with a


keyword global and then it can be known outside.

4.3 Function Inside Function We can call one function inside another
function. For example,

def add(x, y):


return x + y
def cal(x, y, z):
return z*add(x, y)

>>> cal(2, 3, 4)
20

Note: The function add() has been called inside the function cal(). In fact, we
can call any number of functions, any number of times inside a function.

4.4 Recursive Function


We know, a function can call other functions. A recursive function is a
function that calls itself.
# Example:
Factorial:��! = �� ×(�� − 1)× … × 3 × 2 × 1

def facto(n):
if n == 1: return 1 return n*facto(n-1)

>>> facto(5) 120

Note: The factorial, ��! = �� ×(�� − 1)! Writing this way makes it
possible to define through a recursive function.

4.5 Lambda Function

This is another way of defining a function in python.The ‘Lambda function’


is one liner anonymous function. It can take any number of arguments but
allows only one line.

# Single argument
f = lambda x: x**2

# ‘f’ is a given name >>> f(2)


4

Note: The name ‘f’ is an object created through the keyword lambda,
followed by argument x. We can say, we have created a functionf(x).

# Multiple arguments
add = lambda x, y: x + y >>> add(2, 3) 5

# Imported function used. import math


fun = lambda x: x*math.exp(-x)

>>> fun(1)
0.36787944117144233
4.6 Function Overload

Function overloading is the capacity to hold multiple functions with the same
name. Python does not support function overloading by default.

In Python, when we define two functions with the same name, the last
function overrides the first one in the namespace. However, we can give
same function name to different functions when they are inside different
classes or modules. [It is like having same name for two different persons in
the same family is confusing while the same name for two persons in
different families is ok.]

However, we can implement function overloading in Python. [Basically, we


create a wrapper class which wraps any function and then makes it callable
through the built-in special function __call__ and gives a different virtual
identity. In Python, a decorator function wraps a function that allows us to
add more functionality to an existing function without modifying its
structure. We will not discuss more on this here in this book. Interested
readers may always consult articles and books about writing wrapper class
and decorator class.]

Note: There is operator overload implemented in Python. For example, the


‘+’ operator is used for adding two numbers and the same operator is used for
concatenation of two string objects [‘x’ + ‘y’ = ‘xy’].

4.7 About Imported Functions

So far, we have discussed about user defined functions. In Python, we come


across wide variety of functions imported from internal and external modules.
We get to know the online help about them with help().
Example:

Let us import a module pyplot from a third-party package:matplotlib and


check the online help on the plotting function plot().

>>> from matplotlib.pyplot import plot


>>> help(plot)
Help on function plot in module
matplotlib.pyplot:

plot (*args, scalex = True, scaley =True, data = None, **kwargs) …..

Note: A first few lines are displayed here. Let us know about the arguments.
The *args refers arbitrary number of default arguments, **kwargs refers
arbitrary number of keyword arguments and we also find other optional
parameters.
4.8 Python map Function

The map() function returns a map object (which is an iterator) of the results
after applying the given function to each item of a given iterable (list, tuple
etc.).

>>> L = [1, 2, 3, 4]
>>> f = lambda x: x**2
>>> map(f, L)
<map object at 0x0000015A07FD0880> >>> list(map(f, L))
[1, 4, 9, 16]

4.9 Python filter Function

The filter() function returns a filter object where the items from a list (or
tuple etc.) are filtered through a function under some given condition. We can
see the filtered object only when we convert it to a list or tuple etc.

>>> L = [2, 0, 1.0, 10.2, 0.5, 3.4]


>>> f = lambda x: x > 0.5
>>> filter(f, L)
<filter object at
0x0000015A081953A0>
>>> list(filter(f, L))
[2, 1.0, 10.2]

Note that the action of map() over such function will return in Booleans.

>>> list(map(f, L))


[True, False, True, True, False, False]

Application: Given a list of numbers, filter out the floats and return the list of
integers.

>>> L = [56, 2.4, 1.1, 3, 10, 9.6,


-0.8, 91]
>>> list(filter(lambda x: type(x) == int, L))
[56, 3, 10, 91]
4.10 Python zip Function

This built-in function creates an object where the corresponding items from
two lists (or tuples etc.) are paired.

>>> x = ['a', 'b', 'c', 'd'] >>> y = [1, 2, 3, 4]


>>> zip(x, y)
<zip object at 0x0000015A07E955C0> [Zipped object created.]

>>> list(zip(x, y))


[('a', 1), ('b', 2), ('c', 3), ('d', 4)]
Note: The paired items in tuples are formed by corresponding items of the
two lists (x, y).

CHAPTER

5
Data Structures
“It is a capital mistake to theorize before one has Data” – Sherlock Holmes

In this chapter, we learn about some very useful Python concepts. We shall
deal with some Python objects which are basically various forms of data
collections. They are iterables. There are the following built-in data structures
in core Python:

• list [1, 2, ‘potato’]


• string “This is a string”
• tuple (2, 5, -1)
• set {1, 3, 5}
• dictionary {‘a’:1,‘b’:2,‘c’:3}

Data are represented in various forms. To understand them and to use them in
our study, we need to have clear ideas of the above data structures. Handling
Big Data is an important part in Data Science, Machine Learning, and in
studies of basic and social sciences, in fact virtually everywhere. Python
handles structured data very efficiently.

5.1 List

A list is an iterable object. It is a collection of items (numbers, strings or any


other iterable) separated by commas and put inside square brackets [ ]. List is
mutable, that means we can alter a list. [Basically, a list is a data structure in
Python that is an ordered sequence of items and is changeable.]

# List of numbers
>>> A = [3, 2.5, 4.0, -1, 5, 0] >>> type(A)
<class 'list'>

# List of names
>>> B =
[‘Sima’,‘Zakir’,‘Alex’,‘Mita’]
# Mixed list
>>> C = [10, [1, 2], ‘a2’, ‘*@’]

Note: Alphabetic element or anything that is not a pure number, is to be put


inside two quotes. Such a thing is a string.

# Print or display

>>> print(A)
[3, 2.5, 4.0, -1, 5, 0]
>>> B
[‘Sima’, ‘Zakir’, ‘Alex’, ‘Mita’]

Note: To display on interpreter, we need not always useprint()function.

Elements written between two square brackets separated by commas will be


treated as an object called list. Any iterable (a collection of elements) can be
converted into a list by the builtin function named list().

# List by list()function

>>> x = 'abc123' # String >>> list(x) # String to list ['a', 'b', 'c', '1', '2', '3']
# List from a range of numbers >>> list(range(5))
[0, 1, 2, 3, 4]
>>> list() # Empty List []

# To get help >>> dir(list) >>> help(list)

The online listing or help can be obtained through dir() orhelp(). We get to
know about various methods and attributes over list. Methods are some
functions that act on the list objects. Some of the methods will equally apply
on other iterables too. Besides, there are some built-in functions in
(__builtins__) which will also apply over list and other iterables.

5.1.1 Indexing
For the list,
A = [3, 2.5, 4.0, -1, 5, 0]

A list is an ordered sequence. Positions of elements in a list are indexed as0,


1, 2, 3… from left to right. Also, the indexing can be done from right to left
as -1, -2, 3… where the position index of the last (the right end) element is -1.
[One can think of the list be folded to make a ring so that the last element(-1)
on the number line comes to sit on the left to the first element at position 0.
So, we can count the elements from either end.]

List 3 2.5 4.0 -1 5 0


items
Pos 0 1 2 3 4 5
-6 -5 -4 -3 -2 -1

# Elements by indexing
>>> A[0] # position 0

3
>>> A[1] # position 1

>>> A[2]
4.0
>>> A[-1] # The last element 0
>>> A[-2] # The item before the last 4

5.1.2 Slicing

Slicing is to take out a part of a list while keeping the original list unchanged.
Slicing is done through indexing. The general rule of indexing for a list L is
to write:

L[start : stop : step]

The sliced list begins from ‘start’, proceeds in step-sizeof ‘step’ and goesup
to ‘stop’ but it won’t touch or cross ‘stop’ [It is as if to stop before the stop
signal!]. The colon (:) is a separator.

>>> A = [3, 2.5, 4.0, -1, 5, 0] # Item index: 1 to 3 in step 1 >>> A[1:4:1]

[2.5, 4.0, -1]

# Step size = 1, by default >>> A[1:4]


[2.5, 4.0, -1]

# Start from index 0, by default >>> A[:4]


[3, 2.5, 4.0, -1]

# From index 2 until the end >>> A[2:]


[4.0, -1, 5, 0]

5.1.3 Built-in Functions on List >>> A = [3, 2.5, 4.0, -1, 5, 0]

# Length = Total number of items >>> len(A) 6

# Sum of numbers in list A >>> sum(A)


Note: The last element, A[-1] can also be written as A[len(A)-1].
# Python script: Mean of numbers

A = [3, 2.5, 4.0, -1, 5, 0] mean = sum(A)/len(A)


print(mean)

Note: We need not use a for-loop anymore to compute the sum of numbers.
The functionsum() does the same job over the a list of elements.

# Maximum and Minimum

>>> max(A)
5
>>> min(A)
-1

# Maximum
# Minimum
# Sorting a list

# Sorted in ascending order >>> sorted(A) [-1, 0, 2.5, 3, 4.0, 5] # Sorted in


descending order >>> sorted(A, reverse = True) [5, 4.0, 3, 2.5, 0, -1]
>>> A [3, 2.5, 4.0, -1, 5, 0]
[Original list unchanged.]

Note: The built-in functionsorted() leaves the original list unchanged.


[Check the online manual by typing help(sorted) over the interpreter.]

>>> B =
[‘Sima’,‘Zakir’,‘Alex’,‘Mita’]

# Ascending, alphabetically >>> sorted(B) ['Alex', 'Mita', 'Sima', 'Zakir'] >>>


C = ['Good', 10, 'Bad', 50] >>> sorted(C)
[10, 50, 'Bad', 'Good']

Note: For list C above, it sorted first the numbers and then the strings.

>>> D = ['236', '-105', '925', '1000']


>>> sorted(D)
['-105', '1000', '236', '925']

Note: D is a list of strings and the strings are of pure numbers. The sorted()
function first looked for the sign, then the digits from left to right and not the
total number inside the quotes.

5.1.4 List Methods


Methods are some specific functions designed to apply on the object.
From Python Documentation:

A methodis a function that “belongs to” an object. (In Python, the term
method is not unique to class instances: other object types can have methods
as well. For example, list objects have methods called append, insert, remove,
sort, and so on.

# Check the Directory >>> dir(list)

['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__',


'__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__',
'__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__',
'__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__',
'__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__',
'__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__',
'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove',
'reverse', 'sort']

The names with double underscores (__) are either list-attributes or of the
type of wrapper class [A Wrapper class is a class whose object wraps or
contains primitive data types.]. These are at the background of various
operations. For example, __add__does the ‘+’ operation for two lists, which
is concatenation.

>>> a = [1, 2] >>> b = [3, 4]

# Concatenation of a and b >>> a.__add__(b)


[1, 2, 3, 4]

# Concatenation by ‘+’ operator >>> a + b


[1, 2, 3, 4]

Names without underscores refer to the functions applicable to list. These are
known as methods. Methods can work asattribute references to the object.
That means, we can refer list.method_name() where the method_name()
becomes an attribute to the list object.
[The concepts of class, object, attribute referencing etc. will be clear when
they are introduced in Chapter 4. For now, we may understand that a class is
a block of code written in a style and under which there can be functions,
parameters etc. Object is an instant of a Class. In other words, a Class is a
blueprint of an Object.]

We can seek online help to know about any method. This is called
method_descriptor. For example, if we want to know about the method
append() under the objectlist, we type:

>>> help(list.append) Help on method_descriptor:


append(self, object, /)
Append object to the end of the list.
# Location of element

>>> A = [3, 2.5, 4.0, -1, 5, 0] # Location of the element ‘2.5’ >>>
A.index(2.5)

1
>>> B =
[‘Sima’, ‘Zakir’, ‘Alex’, ‘Mita’]

# Location of ‘Mita’
>>> B.index(‘Mita’) 3

Note: The method, index() is in list. So, we can write list.index(A, 2.5),
list.index(B, ‘Mita’) etc. or as A.index(2.5) or B.list(‘Mita’) as attribute.

Application: Find the location of the maximum value in a list.


>>> A = [3, 2.5, 4.0, -1, 5, 0]

# Index of max element >>> A.index(max(A)) 4

Exercise: Create a list of 1000 random numbers between 0 to 1. Find the


maximum and minimum values and locate them.

# Reverse the list

>>> A = [3, 2.5, 4.0, -1, 5, 0] >>> A.reverse()


>>> A
[0, 5, -1, 4.0, 2.5, 3]

⟵ Reversed List
# Sorting
>>> A = [3, 2.5, 4.0, -1, 5, 0]

# Ascending order
>>> A.sort()
>>> A
[-1, 0, 2.5, 3, 4.0, 5]

# Descending order
>>> A.sort(reverse = True) [5, 4.0, 3, 2.5, 0, -1]

Note: The built-in function sorted(list) returns a sorted list and leaves the
original list unchanged. But the list methods sort() and reverse() return no
new list but alter the original list.

# Append element
>>> A = [3, 2.5, 4.0, -1, 5, 0]

# Putting 10 at the end


>>> A.append(10)
>>> A # New list after appending [3, 2.5, 4.0, -1, 5, 0, 10]

>>> B =
[‘Sima’, ‘Zakir’, ‘Alex’, ‘Mita’]
>>> B.append(‘Ajanta’)
>>> B
['Sima', 'Zakir', 'Alex', 'Mita', 'Ajanta']

Application:

A Fibonacci sequence can be generated using append() on a list. Start from


two integers 0 and 1. Every next integer is the sum of two previous ones.
Thus, we generate a sequence:0, 1, 1, 2, 3, 5, 8…. The formula: ����
+��= ����+ ����+��
Steps:
1. Initial list[0, 1]
2. Add last two elements to create the next element.
3. Append new element to the existing list.
4. Repeat the steps 2 - 3 in a loop.

# Python Script: Fibonacci Sequence x = [0, 1]


for i in range(10):
x.append(x[-2] + x[-1])
print(x)

Output:
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
# Pop: Remove item with index

>>> A = [3, 2.5, 4.0, -1, 5, 0] # Pop out item from position 3 >>> A.pop(3)
-1 ⟵The removed element >>> A
[3, 2.5, 4.0, 5, 0]
[List changed, after item removed.]

# Pop out from end

>>> A = [3, 2.5, 4.0, -1, 5, 0] >>> A.pop() # Without reference 0 ←


Removed item from end >>> A.pop()
5
>>> A
[3, 2.5, 4.0, -1]
[After removal of two items from end

Note: Think of a list as a stack of cards. We wish to remove one card at a


time from the top of the stack.

# Remove, item wise


>>> A = [3, 2.5, 4.0, -1, 5, 0]

# Item -1 to be removed. >>> A.remove(-1)


>>> A
[3, 2.5, 4.0, 5, 0]
>>> L = ['a', 'b', 'c', 'd']

# Item ‘b’ to be removed.


>>> L.remove('b')
>>> L # The list now ['a', 'c', 'd']

# Delete a segment
>>> A = [3, 2.5, 4.0, -1, 5, 0]

# Delete the sliced elements >>> del A[1:4]


>>> A
[3, 5, 0]

Note: The del command is used to delete objects. The list is altered after
deleting the sliced part.
# Insert element
>>> A = [3, 2.5, 4.0, -1, 5, 0]

# Insert 100 in position 3 >>> A.insert(3, 100)


>>> A
[3, 2.5, 4.0, 100, -1, 5, 0, 2]

>>> L = ['a', 'b', 'c', 'd']

# Insert ‘xx’ at position 1 >>> L.insert(1, 'xx') >>> L


['a', 'xx', 'b', 'c', 'd']

# Replace an item
>>> A = [3, 2.5, 4.0, -1, 5, 0]

# Item at pos 3 is replaced by 10 >>> A[3] = 10


>>> A
[3, 2.5, 4.0, 10, 5, 0]

# Count
>>> x = [1, 2, 3, 1, 2, 5, 6, 1]

# How many times an item appears? >>> x.count(1)


3
Application
Tossing a coin:

Find out how many times ‘Head’ appears in 1000 trials. Let us assign two
numbers: Head = 1, Tail = 0. We generated a random list of 0’s and 1’s. Then
we apply count() over the list.

>>> x = [random.randint(0, 1) for i in range(1000)]


>>> x.count(1) # Count of 1’s
507

Exercise: Throw a Dice 10000 times. Find out how many times ‘6’ appears.
# Concatenation

>>> A = [3, 2.5, 4.0, 1, 5, 0] >>> B = ['Sima', 'Zakir', 'Alex',

'Mita'] # Joining two lists end to end >>> A + B


[3, 2.5, 4.0, -1, 5, 0, 'Sima',

'Zakir', 'Alex', 'Mita']

>>> x = [2, 3, 5]
>>> 3 * x
[2, 3, 5, 2, 3, 5, 2, 3, 5] [This means: x + x + x]

>>> y = ['a', 'b']


>>> 3*x + 2*y
[2, 3, 5, 2, 3, 5, 2, 3, 5, 'a',

'b', 'a', 'b']


Note: The + operator means concatenation here. 3*x means the list x is
concatenated 3 times.
5.1.5 List Creator

In Python 3, range() is a built-in object: range(start, stop, step)creates


integers from ‘start’ in step of ‘step’ and stops before ‘stop’. The default start
is 0 and default step size = 1. This object can be converted into a list of
integers.
# Create list of integers

>>> x = range(2, 10, 3) >>> x


range(2, 10, 3)

# Convert to list
>>> list(x)
[2, 5, 8]
>>> list(range(10, 0, -2)) [10, 8, 6, 4, 2]

# The step = 1 by default >>> list(range(5, 10)) [5, 6, 7, 8, 9


# Default start = 0 and step = 1 >>> list(range(5))
[0, 1, 2, 3, 4]

# Math operations inside


>>> range(3*4 + 5)
range(0, 17)
>>> range(10, 2)
range(10, 2)
>>> list(range(10, 2))
[] ← Empty list

Note: range() takes integer arguments only. To create a list of floats or


fractional numbers in core Python, we can take help of an explicitfor-loop or
list comprehension method as described next.

[In Python 2, range() creates a list directly. But there is another object called
xrange()in Python 2 which is equivalent to range() in Python 3.]

5.1.6 List Comprehension

Various operations, applications of functions with loop and logical structures


can be embedded inside a list. One of the Python’s most distinctive features
is the list comprehension, which we can use to create powerful functionality
within a single line of code.

Example # 1:
Suppose, we have a list of numbers and we are asked to generate another list
from it where each item will be the square of the corresponding items of the
old list. How can we do that? The usual way is to write a for-loop as follows.

x = [1, 2, 3, 4, 5] y = [] # Empty list to begin with

for i in x:
y.append(i**2) # Appending
print(y) # List of sq elements

# By List comprehension >>> x = [1, 2, 3, 4, 5]

# Loop inside List


>>> y = [i**2 for i in x] >>> y
[1, 4, 9, 16, 25]

Note: The part offor-loop instruction:‘for i in x’ is now embedded in the list.


Example # 2:
From a list of positives and negatives, create a list of absolute values of all
the numbers.

>>> x = [-1, 2, -3, -4] >>> y = [abs(i) for i in x] >>> y


[1, 2, 3, 4]

Example #3:
# List of ��-values (in radian)
>>> from math import sin, pi

>>> theta = [i*pi/2 for i in range(4)]


>>> theta
[0.0, 1.5707963267948966,
3.141592653589793,4.71238898038469
]

# List of sin ��-values from ��-list

>>> [sin(n) for n in theta]


[0.0, 1.0, 1.2246467991473532e-16,
-1.0]

Example #4:
Separate out positive numbers
# With Logical if

>>> x = [-1, 0, 1, 2, -3, 5, -6] >>> [i for i in x if i > 0] [1, 2, 5]

Example #5:Separate out even numbers

>>> L = [2, 9, 12, 23, 46, 78, 45, 94]


>>> [i for i in L if i%2 == 0]
[2, 12, 46, 78, 94]

Exercises:

1. Create a list of 100 random numbers between -1 and +1 and then create a
list of only positive numbers out of it.

2. Create a list of odd numbers from the list of numbers: 12, 19, 91, 36, 39,
41, 11, 9, 103
3. Create a list of negative numbers from the list: -12, 91, -0.5, 0.9, 31, -102,
1.4, 23, 50,

5.1.7 Nested List

There can be lists inside a list. For example, consider the following list:M =
[[1, 2, 3], [4, 5, 6]], where M is a nested list. NowM has two elements each of
which is a list itself. But each of the elements contain 3 elements in them. Let
us try to check on interpreter.

>>> M = [[1, 2, 3], [4, 5, 6]] >>> len(M)


2
>>> M[0] # Element with index 0 [1, 2, 3]
>>> M[1] # Element with index 1 [4, 5, 6]
>>> len(M[0])
3

# Element with index 1 in M[0] >>> M[0][1] 2


# Element with index 2 in M[1] >>> M[1][2] 6

The nested listM can be treated as a Matrix with 2 rows and 3 columns.
M = [[1, 2, 3], [4, 5, 6]]
5.2 String

A string is anything (numbers, alphabets, symbols, spaces) that we put


between two quotes (single or double). The objectstring can be considered as
a data type. Note that in Python 3, any input that the computer takes through
the input() function is astring.

A string, unlike a list, is immutable, i.e., we cannot alter it. We cannot add
any element to a given string. We can slice out a piece from a string but that
is another string. The original string remains unchanged.

# String of digits a = ‘123456’


# String of letters, with a spac b = ‘Python Program’
# A string with mixed characters c = ‘A@23b%C08x’

In the built-in module ( __builtins__) there is an object called str(). We may


convert a collection of numbers or any other iterable into a string by calling
this function.

>>> str(123)
'123'
>>> str([1, 2, 3]) '[1, 2, 3]'

# Iteration over a string


String in Python is iterable like a list. In the following, we run a for-loop
over a string.

>>> for i in 'abc123': print(i, end = ‘ ’)


abc123

5.2.1 Indexing
Let us consider the ‘string’. Positions of the characters:
String012345
-6 -5 -4 -3 -2 -1
Note: The indexing or the positioning of characters in a string is just like that
of a list.
>>> x = 'abcd1234'

# Character in position 2 >>> x[2]


'c'

# Last character >>> x[-1]


'4'

# Each character is a string itself >>> type(x[2])


<class 'str'>

5.2.2 Slicing
>>> x = 'abcd1234'

>>> x[1:6:2]
'bd2'
[Start = 1, Ends before 6, step = 2]

>>> x[2: ]
'cd1234'
[From index position 2 to end.]

>>> x[ : 6]
'abcd12'
[0, 1, 2, 3, 4, 5 elements]

>>> x[ : -1]
'abcd123'
[From start, stops before end]

>>> x[ : ] # Complete string 'abcd1234'


>>> x[: :] # Complete string 'abcd1234'
>>> x[: : -1]
'4321dcba'
[step = -1 makes it from right to left.]
Note: Slicing of a string is also done in the same as with lists. In the last
entry, the step = -1 is to reverse the string.

# Application:

Given any string, find out if it is palindrome or not. [A palindrome is a


sequence which when reverted, appears unchanged. For example, 123454321
is a palindrome number, and tattarratattat is a palindrome word.]

# Python Script: Check palindrome x = '62543253276277267235234526' if x


== x[::-1]:
print('Palindrome')
Output: Palindrome
5.2.3 Built-in Functions on string # Type

>>> x = 'abcd1234'
>>> type(x) # Type of the object <class 'str'>
# Length (Size)

>>> len(x) # Length of the string 8


# Evaluate the string of digits

>>> eval('234')
234
>>> eval('23.56')
23.56
>>> eval('23 + 56')
79
[Evaluates the string of sum.]

>>> int('234') # For integer 234


>>> float('23.56') # For float 23.56

# Enumerate

The built-in object enumerate works on an iterable (list, string or anything)


and returns an enumerate object, which is iterable. For a string, it creates
index vs. string characters.
>>> en = enumerate('abcd1234') >>> en
<enumerate object at
0x000002AEAF1FFB40>
>>> for i in en:

print(i)
(0, 'a')
(1, 'b')
(2, 'c')
(3, 'd')
(4, '1')
(5, '2')
(6, '3')
(7, '4')

5.2.4 Concatenation
Concatenation means, two strings are joined endto-end.We do this with ‘+’
operator.

>>> x = 'abcd1234'
>>> x + x + x
'abcd1234abcd1234abcd1234' # Three strings to concatenate. >>> 3*x
'abcd1234abcd1234abcd1234'
>>> a = 'This is'
>>> b = ' ' # Space as str character >>> c = 'Python 3.'
>>> a + b + c
'This is Python 3.'

# Concatenate and evaluate

# Evaluate a concatenated string >>> eval(‘123’ + ‘456’)


123456

# Addition of evaluated strings >>> eval(‘123’)+ eval(‘456’) 579

5.2.5 Methods on String


# Check the directory for methods >>> dir(str)
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__',
'__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__',
'__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__',
'__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__',
'__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__',
'__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize',
'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format',
'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit',
'isidentifier', 'islower', 'isnumeric', 'isspace', 'isprintable',

'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace',


'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith',
'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']

Note: We can know about the methods with online help(). For example, we
can write help(str.title) on interpreter (>>>) to know about this method.

# Find index of a character >>> x = 'abcd1234'

# Index of a particular character >>> x.find('d')


3

# Check the string >>> x = 'abcd1234'

# Is the string made of digits only? >>> x.isdigit()


False
>>> y = '345'
>>> y.isdigit()

True
# String of literals
>>> s = 'my name is abhijit'

# Returns with all Upper cases >>> s.upper()


'MY NAME IS ABHIJIT'

# Upper case of 1st letter to each word >>> s.title()


'My Name Is Abhijit'
# Only the first character
>>> s.capitalize()
'My name is abhijit'

>>> S = 'I AM LEARNING PYTHON'

# Returns with all lower cases >>> S.lower()


'i am learning python'

>>> A = 'I AM THE MANAGER'

# Replacing bunch of characters >>> A.replace('I AM', 'YOU ARE') 'YOU


ARE THE MANAGER'

# Join string elements


>>> L = ['P', 'y', 't', 'h', 'o', 'n', '3.8']

# Elements are joined with no gap. >>> ''.join(L)


'Python3.8'

# Joined with a character in between >>> '-'.join(L)


'P-y-t-h-o-n-3.8'

# From List to String # List of digits


>>> x = [1, 2, 3, 4]

# Convert each digit to str character >>> xx = [str(k) for k in x] ['1', '2', '3',
'4']

>>> ''.join(xx) '1234'


# Split a String
Splitting is done through a separator which is put as argument inside the
split() method.
>>> s = 'a,b,c,1,2,3'

# Separator is comma (,) >>> s.split(',')


['a', 'b', 'c', '1', '2', '3']

>>> speech = 'Lockdown at my home'


# Separator is a gap
>>> speech.split(' ')
['Lockdown', 'at', 'my', 'home']

Application:
How many sentences are there in a Text? Consider a large text comprising of
many sentences. We can count how many sentences are there.

• Take the text as a string


• Every sentence ends with a full stop. So split the string with this separator
(‘.’).
• Then count the split string elements. [Elements are sentences here.]

Let us take an excerpt from a Ben Okri story:

>>> story = "At the heart of all science – its experiments, its theories, its
mathematics, its discoveries, its interpretations – is the story instinct. The
scientific mind would be impossible without the story DNA, without the
story-seeing brain cells. The mind’s aspects do not operate in isolation. Every
human being immersed in the cyclorama of reality is implicated in the cosmic
story-making nature of reality. Maybe this story-making quality of reality is
what constitutes the heart of our existence. At every moment we are in a
micro or macro 'once upon a time' sea of existence. In every moment we are
part of the infinite stories that the universe is telling us, and that we are telling
the universe."

>>> len(story.split(‘.’))
8 ← The text has 8 sentences.
# Search a pattern

We can search any pattern inside a string through a module called re (re =
regular expression) in core Python.

>>> import re

>>> txt = 'This is a short text written as Python String.'


>>> re.search('written', txt)
<re.Match object; span=(21, 28),
match='written'>

>>> re.search('rotten', txt) [Returns nothing]

>>> help(re.search)
Help on function search in module re:
search(pattern, string, flags=0) Scan through string looking for a match to
the pattern, returning a Match object, or None if no match was found.

Note: Look for other useful functions in re which can be used for other kinds
of search over a string. For example, re.findall(), re.match().

5.2.6 String Format


Writing in style:
# Ordering/Permutation

>>> a, b, c, d, e = 'Ronaldo', 'Messi', 'Maradona', 'Pele', 'Zidane' >>> "


{2}, {0}, {1}, {3},

{4}".format(a, b, c, d, e) 'Maradona, Ronaldo, Messi, Pele, Zidane'


>>> "{0}, {2}, {1}, {3},

{4}".format(a, c, b, e, d) 'Ronaldo, Messi, Maradona, Zidane, Pele'

# Ordering with key words

>>> "{y}, {x}, {z}".format(x = '12', y = '56', z = '72')


'56, 12, 72'

5.2.7 String to List # String of integers >>> x = '1234'

# List of string characters


>>> list(x)
['1', '2', '3', '4’]
>>> [int(i) for i in list(x)] [1, 2, 3, 4] # List of integers

5.3 Tuples

A tuple is a collection of elements (numbers, strings or any other iterable)


separated by comma and written between two round brackets [parentheses
()]. Of course, without parentheses too, items separated by commas form a
tuple. It is a built-in iterable object like list or string. It is immutable.

# Examples of tuple

x = (2, 3, 0.5, 0)
subjects = ('phys', 'chem', 'math') mixed = (12, 'cold', [1, 2], (2, 3)) V = 2,
4, 8 # Without parenthesis

Note: An empty tuple is written as() and a tuple like (10, ) is a tuple with a
single element. [A comma is a must to indicate a non-empty tuple. Without
comma, (10) is just an integer.]

5.3.1 Built-in Functions on Tuple

>>> type(x) <class 'tuple'>


# A tuple of numbers
>>> t = (2, 3, 6, 10, -9, 0)

# Length: total number of elements >>> len(t) 6

# Sum of elements >>> sum(t)


12
>>> max(t)
10
>>> min(t)
-9

# Maximum
# Minimum
5.3.2 Indexing

The idea of indexing is the same as that of list or string. The positions of
elements are counted as0, 1, 2,… from left to right and as-1, -2, 3,…from
right to left.

>>> t[2] 6
>>> t[-1] # Element No.4
-9
>>> 6 in t # To search an element True

5.3.3 Slicing

>>> t = (2, 3, 6, 10, -9, 0) >>> t[2: 5]


(6, 10, -9)
>>> t[1: -1: 2]
(3, 10)

5.3.4 Concatenation

>>> t1 = (1, 2, 3)
>>> t2 = (4, 5, 6)
>>> t1 + t2 (1, 2, 3, 4, 5, 6)
>>> t1*2
(1, 2, 3, 1, 2, 3)

5.3.5 Conversion
>>> x = [1, 2, 3, 4, 5] # List >>> tuple(x) # tuple from list

(1, 2, 3, 4, 5)
>>> y = (1, 2, 3, 4, 5) # Tuple >>> list(y) # list from tuple [1, 2, 3, 4, 5]

Note: Sometimes it is useful to convert tuple into a list because a tuple is not
mutable but a list is. Also note that the built-in functions like type(), len(),
sum(), max(), min() etc. are equally applied over list, string and tuple.

5.3.6 Methods

There are not many methods found on tuple. However, a tuple can be
converted into a list and then the list methods can be applied.

# The online manual

>>> dir(tuple)
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__',
'__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__',
'__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__',
'__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__',
'__reduce__', '__reduce_ex__', '__repr__', '__rmul__', '__setattr__',
'__sizeof__', '__str__', '__subclasshook__', 'count', 'index']

Note: We can also use the built-in methods with double underscores on both
sides. For example, check with help(tuple.__sizeof__).

# Count
>>> t = (2, 3, 4, 1, 2, 5, 3, 2)

# Count how many times 2 appears >>> t.count(2)


3

5.4 Set

A set is an object with unordered collection of unique elements. We can


make a set of elements from other iterables like list or tuple. [In a list or tuple,
the same element can repeat but the set returns the unique elements.]

A set is constructed by putting unique elements, separated by commas, within


curly brackets or braces:{}. The primary objective to have a set is to eliminate
duplicate entries. A set is mutable. We can update a set to make a new set of
elements.

>>> s = {1, 2, 6, 9} # A set


>>> type(s)
<class 'set'>

# A list is converted to set >>> set([1, 2, 4, 1, 2])


{1, 2, 4}
>>> t = (1, 2, 4, 1, 2) # tuple >>> set(t) # Converted to set

{1, 2, 4}
# Binary String
>>> b = '110011101001'

# Set of elements >>> set(b)


set(['1', '0'])

Note: The{} is not an empty set. It is a different class called dictionary. As set
is unordered, so indexing and slicing cannot be done over a set.

Application:
# Python Script: A die is thrown 100 times. Make a set of outcomes.
import random
x = [random.randint(1, 6) for i in range(100)] print(set(x))
Output: {1, 2, 3, 4, 5, 6}

5.4.1 Built-in Function on Set


# Size or length

>>> s = {1, 2, 6, 9}
>>> len(s) # Length 4

5.4.2 Methods

>>> dir(set)
['__and__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__',
'__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__',
'__iand__', '__init__', '__init_subclass__', '__ior__', '__isub__', '__iter__',
'__ixor__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__or__',
'__rand__', '__reduce__', '__reduce_ex__', '__repr__', '__ror__', '__rsub__',
'__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__',
'__xor__', 'add', 'clear', 'copy', 'difference_update', 'intersection',
'intersection_update', 'isdisjoint', 'issubset', 'issuperset', 'pop', 'remove',
'symmetric_difference', 'symmetric_difference_update', 'union', 'update']
'difference',

'discard',
# Update

We can update a set with the union of itself or with any other iterables (tuple,
string, list, dictionary).

>>> s = {1, 2, 6, 9}
# Inserting a set {10}
>>> s.update({10})
>>> s # The set is updated {1, 2, 6, 9, 10}

# Union with another set >>> s.update({11, 21}) >>> s

{1, 2, 21, 6, 9, 10, 11}


# Union with various iterables

>>> s.update([10, 11], {21, 31}, 'name')


>>> s
{1, 2, 'e', 6, 9, 10, 11, 'n', 'a',
21, 'm', 31}

# Pop out element

>>> s = {2, 1, 6, 'x', '12', 0, 1}


# Remove and return arbitrary element
>>> s.pop()
0
>>> s.pop()
1

# Remove an element

>>> s = {2, 1, 6, 'x', '12', 0, 1}


# Element ‘6’ to remove

>>> s.remove(6)
>>> s # Returns unordered {0, 1, 2, 'x', '12', -1}

# Remove the element ‘12’ >>> s.remove('12')


>>> s
{0, 1, 2, 'x', -1}

5.4.3 Adding Two Sets


Addition is done by operator. [Joining two sets by concatenation, like x + y is
not possible.]
# y to add to x
>>> x |= y
>>> x
{1, 2, 3, 5, 6, 7, 8, 10}

# x to add to y
>>> y |= x
>>> y
{1, 2, 3, 5, 6, 7, 8, 10}

5.4.4 Set Theoretic Operations >>> x = {1, 3, 5, 7}


>>> y = {2, 3, 5, 6, 8, 10}
# Union (Take common elements once)

>>> x|y # With operator {1, 2, 3, 5, 6, 7, 8, 10}


>>> x.union(y) # Also by method {1, 2, 3, 5, 6, 7, 8, 10}
>>> y.union(x)
{1, 2, 3, 5, 6, 7, 8, 10}

# Intersection (Taking common elements only)

>>> x & y # With operator {3, 5}


>>> x.intersection(y) # By method {3, 5}
>>> y.intersection(x)
{3, 5}
# Difference

>>> x - y
{1, 7}
>>> y - x
{8, 2, 10, 6}

# Symmetric Difference

>>> x ^ y
{1, 2, 6, 7, 8, 10}
>>> x.symmetric_difference(y) {1, 2, 6, 7, 8, 10}
>>> y.symmetric_difference(x) {1, 2, 6, 7, 8, 10}
5.4.5 Subset
>>> x <= y

False ← x is not subset of y >>> a = {1, 2}


>>> b = {1, 2, 5, 6}
>>> a <= b
True ← a is a subset of b

5.4.6 Frozen Set

Frozen set is just an immutable version of a Pythonset object (A set is


mutable. This means, the elements can be added or removed.).
Thefrozenset() is an inbuilt function which takes an iterable object as input
and makes them immutable. The elements of the frozen set cannot be
modified after creation. Due to this, frozen sets can be used as keys in
Dictionary or as elements of another set.

One can do all set theoretic operations over frozen sets and apply methods as
are done with ordinary sets.

>>> s = {1, 2, 5}

# Update for ordinary set >>> s.update([7, 9]) >>> s


{1, 2, 5, 7, 9}

# Frozen set
>>> s = frozenset(s)

# Update NOT possible


>>> s.update([7, 9])
Traceback (most recent call last):

File "<pyshell#633>", line 1, in

<module>
s.update([7, 9])
AttributeError: 'frozenset' object
has no attribute 'update'
5.5 Dictionary

A dictionary is a Python Object where we have key:value pairs, separated by


commas, placed inside a pair of curly brackets { }. If we call through a key, it
will return us the corresponding value. Thus, we can store values against
keys and later we can extract a value by giving a key. The built-in
objectdict()creates a dictionary. A dictionary is mutable.

# Example
>>> D = {'a':2, 'b':4, 'c':6,

‘x’:10 } >>> type(D)


<class 'dict'>

A dictionary is usually an unordered collection of key:value paired elements.


In the above example, ‘a’, ‘b’, ‘c’, ‘x’ are keys, and 2, 4, 6, 10 are the
corresponding values respectively. The elements are unordered, so it is not
possible to do indexing and slicing over a dict object.

# Iteration over dictionary >>> for i in D:


print(i, end = ' ')
abcx

5.5.1 Built-in Functions on Dictionary # Length of paired items

>>> D = {'a':2, 'b':4, 'c':6, ‘x’:10}


>>> len(D)
4
# Sorting

>>> N = {'abhi':2, 'tapas':1, 'anu':3, 'shyam':5, 'raj':4}

# Sorting of keys, by default >>> sorted(N)


['abhi', 'anu', 'raj', 'shyam',

'tapas'] >>> M = {2:10, 5:12, 4:14, -1:6} >>> sorted(M)


[-1, 2, 4, 5]

5.5.2 Methods on Dictionary


>>> dir(dict) ['__class__', '__delattr__', '__contains__',

'__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__',


'__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__',
'__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__ne__',
'__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__',
'__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'clear',
'copy', 'fromkeys', 'get', 'items', 'keys', 'pop', 'popitem', 'setdefault', 'update',
'values']

# Items, Keys, Values

>>> D = {'a':2, 'b':4, 'c':6, ‘x’:10}


# Items that formed dictionary
>>> D.items()
dict_items([('a', 2), ('b', 4),
('c', 6), (‘x’, 10)])

# Back to dict
>>> dict(D.items())
{'a': 2, 'b': 4, 'c': 6, ‘x’: 10} >>> D.keys() # Keys dict_keys(['a', 'b', 'c', ‘x’])
>>> D.values() # Values dict_values([2, 4, 6, 10])
Note: The attributes:D.items(), D.keys() and D.values() return iterable
objects like dict_items, dict_keys and dict_values respectively. We can print
them under a for-loop.

# Iterating over Dictionary items >>> for i in D.items():


print(i)

('a', 2)
('b', 4)
('c', 6)
('x', 10)

# Calling by the key


>>> example = {'color': 'red', 'fruit': 'cherry', 'taste':
'good'} >>> example[‘fruit’]
‘cherry’
>>> example[‘taste’]
‘good’

The keys and values can be anything - any number or an iterable object. For
example,

>>> C = {(0, 0): 10, (0, 1): 20, (1, 0): 30, (1, 1): 30}
>>> C.keys() # Keys as tuples
dict_keys([(0, 0), (0, 1), (1, 0), (1, 1)])

# pop, popitem, clear

>>> D = {'a':2, 'b':4, 'c':6,


‘x’:10}
# Drops an item through key
>>> D.pop('b')
4
>>> D
{'a': 2, 'c': 6, ‘x’: 10}

# Drops random item (key:value pair) >>> D.popitem()


('c', 6)
>>> D
{'a': 2, 'b': 4}
>>> D.clear() # Clears out all items

{}
# update

>>> D = {'a':2, 'b':4, 'c':6, ‘x’:10}


>>> D.update({'d': 8, 'e': 12})
[Arg is another dictionary.]

>>> D
{'a': 2, 'b': 4, 'c': 6, ‘x’: 10, 'd': 8, 'e': 12}
>>> D = {'a':2, 'b':4, 'c':6, ‘x’:10}
>>> D.update([('d', 8), ('e', 12)])
[Arg is a list of tuples.]

>>> D
{'a': 2, 'b': 4, 'c': 6, ‘x’: 10, 'd': 8, 'e': 10}

# Dictionary 1
>>> d1 = {'a': 10, 'b': 20}
# Dictionary 2
>>> d2 = {'c': 30, 'd': 40}

# Dict 2 is merging with Dict 1 >>> d1.update(d2)


>>> d1
{'a': 10, 'b': 20, 'c': 30, 'd': 40}

Note: The method update() is basically merging two dictionaries. The


merging or concatenation of two dictionaries is not possible by ‘+’ operator
as it is done for a List or String.

# insert, replace single item D = {'a':2, 'b':4, 'c':6}

# New key with value added


>>> D['d'] = 10

>>> D
{'a': 2, 'c': 6, 'b': 4, 'd': 10}
# Replacing value to an old key

>>> D['a'] = 20
>>> D
{'a': 20, 'b': 4, 'c': 6, 'd': 10}

# Copy and Delete


>>> D = {'a':2, 'b':4, 'c':6}
# Copy of dictionary >>> DD = D.copy()

# Deleted with command


>>> del D
>>> D
Traceback (most recent call last):

File "<pyshell#808>", line 1, in <module>

D
NameError: name 'D' is not defined >>> DD
{'a': 2, 'b': 4, 'c': 6}
[Copy of D remains.]

# Delete through key


>>> D = {'a':2, 'b':4, 'c':6}

# Delete through key


>>> del D['b']
>>> D
{'a': 20, 'c': 6}
[Key deleted, key-value pair gone.]

5.5.3 Create a Dictionary

A dictionary can be created by built-in object dict() from a list of pairs(x, y).
The pairs can be in the form of tuple or list or set etc. Each pair is converted
to a key:value item.

# items
>>> a = [(1, 10), (2, 20), (3, 30),

(4, 40)] >>> dict(a)


{1: 10, 2: 20, 3: 30, 4: 40} >>> x = [('a', 2), ('b', 4), ('c',

6)] >>> dict(x)


{'a': 2, 'b': 4, 'c': 6}
>>> color = [['red', 2], ['green',

4], ['blue', 6]] >>> dict(color)


{'red': 2, 'green': 4, 'blue': 6}
5.5.4 Dictionary Comprehension

Like list comprehension, we can also make dictionary comprehension [The


for-loop or logical structures can be put inside curly brackets.].

Examples

>>> X = {i: i+2 for i in range(5)} >>> X


{0: 2, 1: 3, 2: 4, 3: 5, 4: 6}

>>> Y = {k: k**2 + k for k in range(1, 6)}


>>> Y
{1: 2, 2: 6, 3: 12, 4: 20, 5: 30}
>>> color = ['red', 'blue',
'green']
>>> {c.upper(): c.lower() for c in color}
{'RED': 'red', 'BLUE': 'blue',
'GREEN': 'green'}

Application

Create a dictionary of sin�� vs. �� in the interval [−2��, 2��].


from math import pi, sin
sint = {i*pi: sin(i*pi) for i in

range(-2, 3)} print(sint)

Output: (after round off)


{-6.28: 0.0, -3.14: -0.0, 0.0: 0.0, 3.14: 0.0, 6.28: -0.0}

5.5.5 Nested Dictionary

We can have dictionary inside a dictionary. The keys or values of a


dictionary can themselves be dictionaries. In fact, the keys and values can be
anything.

>>> X = {'A': {'a': 1, 'b': 2}, 'B': {'c': 3, 'd': 4}}


>>> X.keys()
dict_keys(['A', 'B'])
>>> X.values()

dict_values([{'a': 1, 'b': 2}, {'c': 3, 'd': 4}])


>>> X['A']
{'a': 1, 'b': 2}
>>> X['B']
{'c': 3, 'd': 4}
>>> X['A']['a']
1
>>> X['B'].values()
dict_values([3, 4])

# Problem: Given a nested Dictionary, iterate over the items and show the
keys and values.

people = {101: {'Name': 'Bhola', 'Age': '21', 'Sex': 'Male'}, 102: {'Name':
'Puchi', 'Age': '18', 'Sex': 'Female'}}

for identity, info in people.items(): print('\nId No.:', identity) for key in


info:

print(key + ': ' + info[key])

Output:
Id No.: 101

Name: Bhola Age: 21


Sex: Male

Id No.: 102 Name: Puchi Age: 18


Sex:Female

5.6 Packing, Unpacking

This is a very useful python concept that we often use in our codes. Any
iterable (list, string, tuple, set or dict) can be thought of as packing of some
elements under a variable name.

We can unpack the elements by assigning the variable name to equal number
of variable names. So, the corresponding elements will be assigned to the
variables one-to-one.

>>> x = '235' # Packing as string >>> a, b, c = x # Unpacking >>> a '2'


>>> b '3'
>>> c '5'

>>> t = (2, 3, 5) # Packing as tuple >>> a, b, c = t # unpacking


>>> L = [2, 3, 5] # Packing as list >>> a, b, c = L # Unpacking

# Packing as dictionary
>>> D = {'x':2, 'y':3, 'z':5} >>> a, b, c = D # Unpacking >>> a
'x'
>>> b
'y'
>>> c
'z'

Note: For a dictionary, unpacking returns the keys only.

5.7 Compare and Search


# Comparing iterables

When two tuples or lists are compared, it checks the first elements of each
only. In case the first elements in both are equal then the second elements are
considered and so on. The output is returned in BooleanTrue and False.

>>> t1 = (2, 7, 1) # tuple >>> t2 = (5, 0, 2)


>>> t1 > t2
False
>>> t1 < t2
True

>>> x1 = [2, 7, 1] # list >>> x2 = [2, 0, 2]


>>> x1 > x2
True
>>> x1 < x2
False
# Searching
Searching through membership operator ‘in’ returns Boolean True and False.

>>> 5 in x1
False
>>> 0 in t2
True

>>> D = {'a':2, 'b':4, 'c':6} >>> 2 in D


False

# Searching through keys only >>> 'c' in D True

# Checking with built-ins


It is sometimes useful to check an iterable whether it contains element 0, or if
it is empty.

x = [1, 2, 0, 3] >>> all(x)


False
>>> any(x)
True

Note:

• all() returns True if bool() of all the elements are True.


• any() returns True if bool() of at least one element is True.

The bool()of empty object (no argument) or bool(0) returns False, otherwise
True.

5.8 Copy Objects


No copy
In Python, we often copy some object as follows.

>>> x = 2
>>> y = x
>>> id(x)
140720007485120

>>> id(y)
140720007485120

However, this is not a real copy. We created a new reference for the same
object which is evident from the same identity for two names which we do
not realize. If we now assign x to another number, the id(x) becomes new but
the assumed copy, y retains the old value and identity.

The same is not true for a mutable object like list, tuple etc. Let us create a
listx, copy it to y and then alterx to see what happens.

>>> x = [1, 2, 3] >>> y = x


# Change one element in x
>>> x[0] = 10

# x changes
>>> x [10, 2, 3]
# y also changes
>>> y [10, 2, 3]

Note:y is not really a copy ofx. For real copy, we use built-in copy() or
importcopy module.
Shallow copy
>>> import copy >>> x = [1, 2, 3]
# Real copy y of x
>>> y = copy.copy(x)

We can now change any element of x or add new element to it, the copied
listy remains unchanged. However, this is just a shallow copy. Let us check
this for a nested list.

>>> x = [[1, 2], [3, 4]] >>> y = copy.copy(x)


# One element in x changed

>>> x[0][1] = 0 >>> x # New x [[1, 0], [3, 4]]


>>> y [[1, 0], [3, 4]]
[The copy y also changes.]

The shallow copy poses a problem for objects like a nested list. It creates a
new collection object but the nested elements (the child objects) are not
actually copied (This is only one level deep.), they refer to the old. So,
changing an element will also show up in the copy. However, if new
elements are appended to x, the old elements are not altered. Then y remains
unchanged.

Deep copy

The real deep copy is made when a new collection object is created and it is
recursively populated with the child objects taken from the original.

>>> x = [[1, 2], [3, 4]]


# Deep copy
>>> y = copy.deepcopy(x)

# One element in x changed


>>> x[0][1] = 0 >>> x # New x [[1, 0], [3, 4]]
>>> y [[1, 2], [3, 4]]
[The copy y is unchanged.]

Exercises:

1. Write a one-line Python instruction to find out sum of all odd integers
between 1 to 1000.
2. Find the sum:∑��3, for�� = 1, … 100 by list comprehension.

3.
Check that the following sum:

2��
3�� converges

towards 2, if we take �� = 1.
4. Given a dictionary {‘a’:10, ‘b’: -5, ‘c’:2,
‘d’:0.5, ‘e’: -1.5, ‘f’:13}, find out the
maximum and minimum values in this. 5. Create a list of odd integers
between 1 to 100.
Compute the sum of the numbers in the list. 6. Begin with an empty list, add
10 given
numbers, one by one, inside a for loop. Print
those numbers. Obtain the sum of squares of
those numbers. Print the sum.
7. Consider the List: L = [1, 4, 9, 16, 21, 25, 32,
48, 51]. Write a simple python program (may
be in one line) to obtain a new list which will
consist of only the odd integers.
8. A group of musicians named: ‘Ravi’, ‘Bikram’,
‘Zakir’, ‘Laksmi’, ‘Bhimsen’, ‘Rashid’, and
‘Ajay’, and their code numbers are 001, 002,
003, 004, 005, 006, and 007 respectively.
Create a dictionary with this information and
print to check the code of a certain musician. 9. Given the list of numbers:X =
[0, 3, -1,
10, 8, 7, 11, 100, -10, 36] and
find out the mean and median of the numbers. 10. Define the function
��(��)= ��2+ �� in Python
lambda function way. Use this function to make
a list of values for x in [0, 10] with list
comprehension.
11. The y-coordinate for a projectile motion:�� =

��
tan
�� −
��

��2. Consider the angle, �� = 49�� and initial speed


2�� 02cos2��.
��0= 2 m/s. Make a dictionary with x:y for x values, 0, 1, 2, 3, 4, 5.

12. Write a Python code to compute the sum of digits of 87694.


13. Given a function, �� = ��(��)= 2��3exp (−��2), generate two
lists for(��, ��) data points for the �� range (0, 20), and store data in a
file.
14. Write a Python script to compute the variance of the following numbers:
-2.0, 0, 1.2, 6.1, 4.2, 3.5, -1.9, -3.2, 2.8, 5.3, -0.8
15. Given the numbers: 3, 2, 1, 0, 3, -5, 10, 3, 2, 1, 3, 5, 3, find how many
times 3 appeared.
16. Given 10 numbers: 0, 48, 34.7, −13, 0.5, −10, 100, 305, 9, 23. Write a
python script to find out the number of numbers that are in between 0 and 50.
17.For the string ‘123456809’, print the characters evaluate the sum of the
characters.
18. Write a Python Script to count how many vowels are there in the string:
‘university’.
19. Read 9, 0, −1, 4 as input from outside and then 10, −3, 7, 9 as another set
of input from outside. Make two lists. Add the two lists. Sort the joint list by
system function.
20. Write a Python program to unzip a list of tuples into individual lists.
21. Given, �� = [1,2,3, [], [1,2,3], 'a', (4,5), {2,3}, {}]. Now, what is the
type of object��? Find out the types of each of the elements in ��.
22. Find what the following line does:[i for i in x if i].
23. How many digits and letter are there in ‘Python3.8’?
24. Read the number 3477843 as a string and check if that is a Palindrome
number.
25. Find the list of numbers (0 < �� < 100) for which the products of their
digits are even.
26. Given some occurrence of numbers: 10, 20, 50, 50, 10, 20, 100, 30, 100,
100, 200, 30, 40, 10, 20, 100, 50, 10, 200, find out how many distinct
numbers are there in the series.
27. Write a Python script to generate a dictionary that contains numbers
between 1 and 10 as keys and values between 12 and 102.
28. Write a Python script to prepare a dictionary from a string: ‘PYTHON’.
29. A list of integers is given: [1, 2, 5, 9, 11, 12, 4, 8, 10, 20, 6, 34, 22, 17, 13,
100]. Filter out the odd numbers and make a new list.
30. Consider two dictionaries:d1 = {‘a’:10, ‘b’:20, ‘c’:30} and d2 = {‘a’:100,
‘b’:200, ‘c’:300}. Prepare a combined dictionary where the values for the
same keys are added. [First, try an elementary code. Then you may use the
function counter() from the module ‘collections’ or any other suitable
module.]
31. Given the list of numbers: [−1, 4, −3.8, −8.9, −10, 10, 22, 9, −2, 9.2, 3.6,
−0.5], write a python script to read the list and print two separate lists
consisting of positive and negative numbers.
32. Check what the following program does: def f(x):
if x == 0:
return 1
return x*f(x-1)
x = int(input())
print(f(x))

Enter�� = 10 and see the output. Make a list of the digits.


33. The following program is to generate Fibonacci

Numbers.
def f(n):
if n == 0: return 0
elif n == 1: return 1 else: return f(n-1) + f(n-2)

n = int(input())
print(f(n))
Now modify the script to generate a list of 20
Fibonacci numbers.
34. The code:print(sum[(i**2 for i in

range(1, 20))]) may give errors. Why? Type this out on your interpreter and
try to understand from error message.

35. Write two programs that will ask the user for positive integers �� and
�� (with �� < �� ) as

inputs and print out ∑����=����2−1. Your first��4+1


program should use an explicit for loop and the

second should use list comprehension. 36. Type the following lines of code
on the python
interpreter (in the order given).
a = [1, 2, 3, 4]
a.append(7)
a.extend([8])
b = a[1:3]
a.extend(b)
a.reverse()
c = a[-5:-1]
a.append(c[2])
What are the values of the lists a, b, and c now? Explain step by step. (You
may have to look up help(list) in the interpreter to see what the individual
commands do.)
37. Type the following lines of code and check what you get.
s ='He can carry a very big book'
t = s.replace('ry','xyz')
l = [0,1,2]
m = l.append(3)
u = '+'.join(l)
v = eval(u)
w = eval('*'.join[i**2 for i in range(1,5)])

CHAPTER

6
Class, Object, Module

“ Our object in the construction of the state is the greatest happiness of the
whole, and not that of any one class.” - Plato

Python is an Object Oriented Programming (OOP) language at the core.


Almost everything in Python is an Object. Usually, for a simple and small
code, we write procedural programming. But for a long and complex code it
is advantageous to organize the blocks of codes in terms of OOP.

The concepts of Class and Object are at the heart of any OOP language. A
class is a blueprint from which individual objects are created.

6.1 Class/ Object

First, we create a Class ( i.e., the blueprint) and then we create objects from
it. So, a class can be thought of as a constructor of an object. On the other
hand, an object is an instance of a Class.

A class is a block of code consisting of two basic features:


• Attributes (variables, parameters, strings)
• Methods (User defined functions, other class inside it)

A class is created with the key word class. We can give any name to a class.
To understand better, we think of the Car example. Thousands of cars of
different types are made. Each car was built from the same set of blueprints.
A particular car is an instance (object) of the class Car.

# Empty class

class Car: pass


>>> type(Car)
<class 'type'>
>>> Car
<class '__main__.Car’> >>> p = Car()
>>> p
<__main__.Car object at 0x000001DB5F75EB20>
>>> q = Car()
>>> q
<__main__.Car object at 0x000001DB5F75EE50>

Note:

We have created a trivial empty class Car [Imagine, we have just created the
structure of a car, no parts added yet.]. The key word pass isPython’s way of
saying, ‘move on’. Also, we have created two objectsp and q (at two memory
locations). Note that Car is a class butCar() is an object where nothing is as
yet to put as argument in the parenthesis.
Next, we include__init__() method in a class. This is generally used to create
objects by providing instance attributes. The __init__() method is
automatically invoked when an object is created from the class. We pass
attributes (variables, parameters) for a particular object (i.e., an instance of
the class) through this.
A Class function that begins (and ends) with double underscore (__) is called
built-in special function and it has a special meaning. There are built-in class
attributes like __name__, __doc__ etc.

# Class with __init__() method/ constructor


class Car:
def __init__(self): pass

Note: In every method inside a Python class,self is used as a reference to the


object of the class. It is a default/ first argument. We do not need to pass self
as argument while calling the function (as object). Python will automatically
include it.

# Class with attributes


class Car:

‘ A simple class for 4-wheeler cars.’


# Class attribute
wheels = 4

# Constructor to initialize def __init__(self, color, model): self.color = color


self.model = model

Note: The parameter wheels can be called as class attribute as it is common


to any method (or functionality) that will be added later. Think of all the cars
that are 4-wheelers. Every 4-wheeler car is an instance or object of the class
Car.

We now add attributes, color and model through the __init__() method so
that we may have cars of various colors and models. Out of the three
arguments, the first argument self is common and it is a kind of pointer. We
pass two other arguments color and model through this pointer. These can be
thought

whereas wheels
of as instance acted as class attributes attribute.
# Documentation string for the class Car
>>> Car.__doc__

>>> Car.__doc__
wheeler cars.'

To Create Objects:

Now we create individual cars of various colors and models. The doc-string
for the class is the same for that of any object.

# An object created: a new Car

>>> C = Car(‘blue’, ‘ Sedan’) >>> C.__doc__


>>> C.__doc__
wheeler cars.'

# Accessing the attributes

>>> C.color
blue
>>> C.model
Sedan

# Modify attribute

>>> C.color = ‘red’ >>> C.color


red

# New methods added to the class Car class Car:

‘ A simple class for 4-wheeler cars.’


# Class attribute
wheels = 4

# Constructor to initialize def __init__(self, color, model): self.color = color


self.model = model
# Method 1
def display(self):
print(‘This is a’,
self.color, self.model)
# Method 2
def change(self, color): self.color = color # Method 3
def brake(self, way):
print(‘This car is
braking’, way)
Let us create an object (a particular car) and investigate the attributes.

# A new Car: an object is created. >>> C = Car('red', 'Sedan')


>>> C.color
'red'
>>> C.model
'Sedan'
>>> C.display()
This is a red Sedan
>>> C.change('black')
>>> C.display()
This is a black Sedan
>>> C.brake('fast')
This car is braking fast

Note: In the methoddisplay(), we do not give any argument as the only


(default) argument is self. In the next method change(), we give the argument
which is drawn through self. But in the last method brake(), the argumentway
is new and it is not referred through self.

To demonstrate the idea of class and object further, we go through more


examples in the following.
# 1 A simple class

class One:
x = 10

# A class named ‘One’ is created >>> One


<class '__main__.One'>

# Attribute Reference >>> One.x


10
# Object ‘p’ is created. >>> p = One()
>>> p
<__main__.One object at 0x000001F613B50250>
>>> p.x
10

#2 Class with Doc String

class Two:
'This is my Class' x, y = 2, 3
z = x*y

# Nothing to be given as argument >>> p = Two()

# To see the Doc String >>> p.__doc__


'This is my Class'
>>> p.x
2
>>> p.y
3
>>> p.z
6

#3 Class with initialization (built-in Method/ Constructor)

class Three:
x = 10
def __init__(self, y):

# Object ‘p1’ is created >>> p1 = Three(20)


>>> p1.x
10
>>> p1.y
20

# Another object ‘p2’ created >>> p2 = Three(30)


>>> p2.y
30
Note: In the above, two instances of the class Three are created. In this,x is a
global variable and y is local variable. One can also say, x is a class attribute
and y is the object attribute.

#4 Class with a function definition added class Four:


“““This is a better Class”””
def __init__(self, x, y): self.x = x
def add(self):

“““This is for addition””” z = self.x + self.y


return z

>>> p = Four(5, 10)


>>> p.__doc__
'This is a better Class'

# Doc string for the Class >>> Four.__doc__


'This is a better Class'

# Doc string for the method >>> Four.add.__doc__


' This is for addition' >>> p.x
5
>>> p.y
10
>>> p.add()
15

#5 One more function added


class Five:
"This is even a better Class"

def __init__(self, x, y): self.x = x


self.y = y

def add(self):
z = self.x + self.y return z

def rms(self):
import math
d = math.sqrt(self.x**2 +

self.y**2) return d

>>> p = Five(3, 4)
>>> p.__doc__
"This is even a better Class" >>> p.add()
7
>>> p.rms()

5.0

Note: The first argument in any method can be anything in place ofself. We
may use any name we think asself is a dummy variable. But we prefer to use
self as convention. For example, the following class is as good as the above.

class Five:
"Here is some
modification over Class Five."

def __init__(self, x, y): self.x = x


self.y = y

def add( arg):


z = arg.x + arg.y return z

def rms( par): import math d =


math.sqrt(par.x**2 +

par.y**2) return d
6.1.1 Deleting Objects and Attributes

# Deleting variable
>>> del p.x
>>> p.x
Traceback (most recent call last):

File "<pyshell#99>", line 1, in


<module>
x
NameError: name 'x' is not defined
>>> p.add()
Traceback (most recent call last): File "<pyshell#100>", line 1, in
<module>
p.add()
File "<pyshell#96>", line 8, in
add
z = arg.x + arg.y
AttributeError: 'Five' object has
no attribute 'x'
# Variable reassigned >>> p.x = 6
>>> p.add()
10

# The Object is deleted


>>> del p
>>> p
Traceback (most recent call last):

File "<pyshell#104>", line 1, in

<module>
p
NameError: name 'p' is not defined

# Another Object w is created >>> w = Five(3, 4)

Note: In the above, we deleted the objectp. In this way, the class remains but
the object is deleted only. One can create another object from the same class.
A class is a blueprint. So, in a way, the blueprint remains intact but the copies
of it can be created or destroyed.

6.1.2 Inheritance
• Parent Class
• Child Class
Inheritance allows us to define a class that inherits all the methods and
properties from another class. A child class is inherited from a parent class.

# Empty Inherited Class class Six(Five): pass

>>> p = Six(2, 3) >>> p.add()


5
>>> p.rms()
3.605551275463989

Note: The classSix is a child class of the parent class Five.


# Variables inherited class Six(Five): “This is a child class.”

def __init__(self, x, y): # From the Parent class Five.__init__(self, x, y)


self.z = self.x * self.y

>>> p.__doc__
‘This is a child class.’ >>> p = Six(2, 3)
>>> p.x, p.y, p.z
(2, 3, 6)

# Method added
class Six(Five):
def __init__(self, x, y, z): # x, y inherited through Five

Five.__init__(self, x, y) # z is not inherited


self.z = z

def square(self): return self.z**2

# Object created out of child class >>> q = Six(2, 3, 4)


>>> q.x, q.y, q.z
(2, 3, 4)
>>> q.square()
16

6.1.3 Class with Iterator

We have seen that we can print the elements of an iterable under a for-loop.
There are two built-in functions that make this iteration possible. They are
iter() and next(). For example,

>>> x = [2, 4, 6, 8]
# Make an iterator >>> xit = iter(x)

# Get the elements >>> next(xit)


2
>>> next(xit)
4
>>> next(xit)

6
>>> next(xit)
8
>>> next(xit)
Traceback (most recent call last): File "<pyshell#405>", line 1, in <module>
next(xit)
StopIteration

In Python, any object, which contains the two methods __iter__(), and
next(), can act as an iterator.

To Create an iterator Object:

class Numbers:
def __init__(self, n): self.i = 0
self.n = n

def __iter__(self): return self

def next(self): if self.i < self.n:


i = self.i
self.i += 1
return i

else:
raise StopIteration()
>>> x = Numbers(4)

# x is now an iterator object >>> x.next()


0
>>> x.next()
1
>>> x.next()
2
>>> x.next()
3
>>> x.next()
Traceback (most recent call last):

File "<pyshell#103>", line 1, in <module>


x.next()
File "<pyshell#96>", line 15, in next
raise StopIteration() StopIteration
6.2 Module

Any Python Script is a module. Let us consider the following simple Python
script that is saved in our directory. This consists of a simple class only.

#1 Python Script:test.py

class MyClass:
"This is a test class" x = 3
y = x*x
s = 'My test Class’

# Import the module >>> import test

# The file test.py is now a module. >>> type(test)


<class 'module'>
# Create Object p. >>> p = test.MyClass

# Attributes
>>> p.x
3
>>> p.y
9
>>> p.s
'My test Class'

#2 Python Script: algebra.py # Python Script: algebra.py import math

class vector:
def __init__(self, vx, vy, vz): self.x = vx
self.y = vy
self.z = vz

def norm(self): xx = self.x**2


yy = self.y**2
zz = self.z**2
return math.sqrt(xx + yy + zz)

# Import the module ‘algebra’ >>> import algebra


>>> u = algebra.vector(2, 3, 4)

# Object u is created. >>> type(u)


<class 'algebra.vector'> >>> u
<algebra.vector object at 0x0000021C83E6FA60>
>>> u.norm()
5.385164807134504

In the above example code, algebra.py is a module where vector is a class


and norm() is afunction.

Note:
The key word ‘self’ (It can be any name. But customarily, it is given.) is a
kind ofpointer (as used in C++). We incorporate the variables from the
__init__ function through this reference. For the arguments in__init__(self,
vx, vy ,vz), apart from the pointerself, the rest come as attributes.

>>> dir(algebra)
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__',
'__package__', '__spec__', 'math', 'vector']
>>> help(algebra.vector)
Help on class vector in module algebra:

class vector(builtins.object) | vector(vx, vy, vz)


|
| Methods defined here: |
| __init__(self, vx, vy, vz) | Initialize self. See

help(type(self)) for accurate signature.


|
| inner(norm)
|
| norm(self)
|
| --------------------------------
------------------------------------
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)

6.3 Package

Packages are namespaces which can contain multiple packages and modules
themselves. They are simply directories, but with a twist.

Each package in Python is a directory with an essential file


called__init__.py. This file can be empty, and it indicates that the directory it
contains is a Python package, so it can be imported the same way a module
can be imported.

In the subsequent chapters, we will encounter several third-party packages


developed for Python like NumPy,SciPy and Matplotlib, Pandas, each of
which contains various modules, classes, and functions. We will import the
packages or a specific module from package for our purpose and use the
functions in it.

Note:

Python can be used to write codes for functional computing as well as in the
way of Object Oriented Programming (OOP). In some chapters, we will
mostly concentrate upon writing short functional programming. Writing
codes in OOP way will be demonstrated too. However, anything in Python is
an object. So, we make use of the objects to write clean and simple codes for
our purpose of Scientific Computing.

CHAPTER

7
Drawing with Turtle
“The inside of a computer is as dumb as hell but it goes like mad!” – Richard
P. Feynman

Let us draw some geometrical figures by the Python module, turtle. This
preinstalled Python library enables users to create pictures and shapes by
providing them with a virtual canvas. Imagine that the cursor/ pen is the
‘turtle’. After we import the module, various functions in it are employed to
direct the turtle to move and draw as we wish. As we begin to type
instructions, a new window or a canvas is opened, and the figures are drawn.

# Directory
>>> import turtle
>>> dir(turtle)

['Canvas', 'Pen', 'RawPen', 'RawTurtle', 'Screen', 'ScrolledCanvas', 'Shape',


'TK', 'TNavigator', 'TPen', 'Tbuffer', 'Terminator', 'Turtle',
'TurtleGraphicsError',
'TurtleScreen', 'TurtleScreenBase', 'Vec2D', '_CFG', '_LANGUAGE', '_Root',
'_Screen', '_TurtleImage', '__all__', '__builtins__',
'__doc__',
'__forwardmethods', '__loader__',
'__methods',
'__package__',
'__stringBody',
'_make_global_funcs', '_screen_docrevise', '_tg_screen_functions',
'_tg_turtle_functions',
'_tg_utilities', '_turtle_docrevise', '_ver', 'addshape', 'back', 'backward',
'begin_fill', 'begin_poly', 'bgcolor', 'bgpic', 'bk', 'bye', 'circle', 'clear',
'clearscreen', 'clearstamp', '__cached__',

'__file__', '__func_body', '__methodDict', '__name__', '__spec__',


'_alias_list',

'_tg_classes', 'clearstamps', 'clone', 'color', 'colormode', 'config_dict',


'deepcopy', 'degrees', 'delay', 'distance', 'done', 'dot', 'down', 'end_fill',
'end_poly', 'exitonclick', 'fd', 'fillcolor', 'filling', 'forward', 'get_poly',
'get_shapepoly', 'getcanvas', 'getmethparlist', 'getpen', 'getscreen',
'getturtle',
'getshapes',

'goto', 'heading', 'hideturtle', 'home', 'ht', 'inspect', 'isdown', 'isfile', 'isvisible',


'join', 'left', 'listen', 'lt', 'mainloop', 'math', 'mode', 'numinput', 'onclick',
'ondrag', 'onkey', 'onkeypress', 'onkeyrelease', 'onrelease', 'onscreenclick',
'ontimer', 'pd', 'pen', 'pencolor', 'pendown', 'pensize', 'penup', 'pos', 'position',
'pu', 'radians', 'read_docstrings', 'readconfig', 'register_shape', 'reset',
'resetscreen', 'resizemode', 'right', 'rt', 'screensize', 'seth', 'setheading', 'setpos',
'setposition', 'settiltangle', 'setundobuffer', 'setup', 'setworldcoordinates', 'setx',
'sety', 'shape', 'shapesize', 'shapetransform', 'shearfactor', 'showturtle',
'simpledialog', 'speed', 'split', 'st', 'stamp', 'sys', 'textinput', 'tilt', 'tiltangle',
'time', 'title', 'towards', 'tracer', 'turtles', 'turtlesize', 'types', 'undo',
'undobufferentries', 'up', 'update', 'width', 'window_height', 'window_width',
'write', 'write_docstringdict', 'xcor', 'ycor']

Note: We can seek help on individual function in turtle by typing help() on


interpreter.
We may also refer the Turtle() class and link the functions in it.
>>> t = turtle.Turtle() # Turtle() Class renamed as t
>>> t.forward(100)
# Instruction to go forward 100 steps
For our subsequent demonstrations, we import everything and so no reference
will be made.

# Import and draw


# Import everything

>>> from turtle import *


>>> forward(100)
# Turtle moves forward by 100 pixels.

Suppose this is a canvas (For demonstration only, not actual). The turtle
draws a line with a default arrowhead that moved from left to right along
xaxis (East to West) by 100 pixels (length of arrow). [Usually, the starting
position is at (0, 0), the centre of the canvas.]

Next, we may instruct the turtle to turn left (the arrow rotates left) by some
angle. Again, we can instruct the turtle to move some steps forward, then turn
right and so on. geometrical figures. Thus, we draw various

# Draw a Square >>> forward(100) >>> left(90)


# left turn by 90
>>> forward(100) >>> left(90)
>>> forward(100) >>> left(90)
>>> forward(100) >>> left(90)
# Python Script: Draw square for i in range(4):
fd(100) lt(90)
# Python Function: square

def square(L):
for i in range(4): fd(L)
lt(90)

return
# Some functions

fd() # forward: forward() bk() # backward: backward() lt() # left turn: left()
rt() # right turn: right() reset() # Reset Drawing Canvas pos() # Position of
Turtle goto() # Turtle to position home() # Turtle returns home. penup() #
Turtle lifts the pen. pendown() # Turtle downs the pen. reset() # Reset the
Canvas

(Erase the previous drawing)

# Some sequential steps


# Turtle to move without scratch >>> penup()

# Go to the position (0, -100) >>> goto(0, -100)


# Downs the pen to draw >>> pendown()
# Moves forward by 200 steps. >>> fd(200)

# Check the position >>> pos()


(200.00, -100.00)

# Go to position (200, 300) >>> goto(200, 300)

# Check position again >>> pos()


(200.00, 30.00)
# Bring back home

>>> home()

# Position now >>> pos()


(0.00, 0.00)

# Shape of the turtle


# Pointer looks like a turtle shape('turtle')
# Square head shape('square')
# The default arrow head shape(‘classic’)
# Speed (How fast Turtle draws)

speed(‘fastest’)
speed(‘fast’)
speed(‘normal’)
speed(‘slow’)
speed(‘slowest’)
Also, numbers from 1 to 10 as argument for various animation speed for
drawing and turning.
# Visibility

# Turtle can be made invisible. hideturtle()


showturtle()

# Setting up Canvas # Set up Screen Screen()


# Set up size of Canvas screensize(800, 600)
# Screen/ Canvas size
setup(width = 600, height = 600)
# Background color bgcolor('green')
# Python Script: Drawing Cross speed('slowest')
for i in range(4):

fd(100)
rt(90)
fd(100)
lt(90)
bk(100)
rt(90)

Note: We setspeed(‘slowest’) so that we can follow the drawing process


clearly.
# Python Function: Equilateral triangle

def triangle(L):
for i in range(3):
fd(L)
lt(120)

return
# Equilateral triangle of side 100 >>> triangle(100)
# Python Function: Hexagon def hexagon(L):
for i in range(6): fd(L)
lt(60)
return
# Draw hexagon of side 100 >>> hexagon(100)
# A general Polygon

def polygon(L, n): angle = int(360/n) for i in range(n):

fd(L)
lt(angle)
return
# Drawing polygons

polygon(100, 4) # Square polygon(100, 6) # Hexagon polygon(100, 8) #


Octagon

We can create patterns with polygons. The functions square(), polygon() can
be used to create various designs. We can create background colors, draw
with colored pens etc.

# Pattern with Polygon

speed(10)
for i in range(50):
polygon(100, 4)
lt(10) # Poly turns left by 10 deg

Fig. Pattern with polygon


# Python Script:
Draw Four colored Boxes.

bgcolor('black') width(4)
penup()
goto(-200, 0) pendown()

# Background Black # Width of line

colors = ['red', 'yellow',


'orange', 'purple']
for i in range(4): color(colors[i]) square(50)
penup()
fd(100)
pendown()

# Fill a square with color begin_fill()


# picks colors # Draw Square
# Filling with color yellow fillcolor('yellow')

# Square of side 100 square(100)


end_fill()

# Python Script: Draw Box from turtle import * speed('slowest')


square(100)

lt(30) fd(100) rt(30)

square(100)

lt(90)
fd(100)
lt(90+30) fd(100)
rt(30+180)
fd(100)
lt(30)
fd(100)
rt(90+30)
fd(100)
rt(60)

fd(100)

Tips: Slow the speed and follow the steps to find out how the cube is being
drawn.
# Python Script: Coiling Squares

bgcolor('black')
pencolor('yellow')
width(3)

for n in range(200, 20, -20): for i in range(2):


fd(n)
lt(90)

Fig. Coiling squares

# Circle, Dot
# Circle of radius = 100
circle(100)

# Circle on downside
circle(-100)
# Arc that subtends angle 90 circle(100, 90)
# Dot (filled) of size 100
dot(100)
# Python Script: Patterns with Circles

for i in range(30):
circle(5*i)
circle(-5*i)
lt(i)
Fig.
Pattern with circles
# Python Script: Concentric Circles def con_circle():
for radius in range(100, 20,

20): circle(radius)
lt(90)
penup()
fd(20)
pendown()
rt(90)

return
con_circle()
# Python Script: Asterisk
width(2)

for i in range(5):
fd(200)
rt(144)

# Python Script: Star of rays


def star(n, r):

""" draw a star of n rays of length r"""


for k in range(0, n):
pendown()
fd(r)
penup()
bk(r)
left(360/n)

width(3)
star(6, 100)
# Python Script: Yellow star

begin_fill()
pencolor('black')
while True:

forward(200)
left(170)
if abs(pos()) < 1:

break
color('yellow')
end_fill()

# Python Script: Nautilus (Spiralling of a square)

L = 200
for i in range(100): square(L)
lt(10)
L = L*0.98
Fig. Nautilus
Random Walk

Study of Random Walk (RW) is very important in Physics. Many random


phenomena in Nature including random Brownian motions of particles in a
gas or air can be explained by the study of RW. So, a computer generated
RW may help us to study. In the following, we generate a RW on a square
grid.

The turtle moves randomly with a certain step (step size given) in either of
the four directions (right, left, up and down). To move right or left, the turtle
moves through fd() orbk() functions. To move upwards, the turtle must turn
left by 90 degree and then to move forward. Similarly, to move down, it must
turn right by 90 degree and then to move forward.

# Python Script: RW
import random
dot(5) # Starting: Dot of size 5

for i in range(5000):
# Random integer 1, 2, 3, 4 n = random.randint(1, 4)

if n == 1:
fd(5)
if n == 2:
bk(5)
if n == 1:
rt(90)
fd(5)
if n == 2:
lt(90)
fd(5)
dot(5) # Ending: Dot of size 5

Fig. Random Walk (5000 steps) on a square grid. Two big dots are for
starting position and the end of journey of the turtle.

# Python Script: RW with random turns

from random import randrange


for i in range(1000):
# Random value between 0 and 360
angle = randrange(360)

rt(angle)
Fig. RW with random turns. The turtle moves with fixed step size but it can
rotate by any angle between 0 to 360 degree. Evidently, the walk is not on a
square grid.

# Colorful Random Walk

For a colorful presentation of the above one may try the following. The pen
color used in the following code is a combination of three primary colors Red
(R), Green (G) and Blue (B):pencolor((R, G, B)). Each of the arguments (R,
G, B) can take any value between 0 and 1 through the uniform random
number generator uniform(). For example, pencolor(0.3, 0.5, 0.1) creates one
particular (mixed) color. We can play around with various combinations.

# Python Script

from random import uniform, randrange


speed('fastest')
bgcolor('black')

for i in range(1000):
pencolor((uniform(0, 1), uniform(0, 1), uniform(0, 1))) angle =
randrange(360)
rt(angle)

Fibonacci Spiral

To generate Fibonacci Spiral, we first need to generate Fibonacci number


series. Fibonacci series: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, .. Every number is the
sum of previous two numbers.

To draw the Fibonacci Spiral, we need to create squares with sides following
Fibonacci series.

We draw one square and then a corner-to-corner arc. The pen starts to draw
another square with arc from the last position and so on.

# Python Function: Square with arc

def sq_arc(L):
square(L)
circle(L, 90)

Note: We used the square(L) function which is followed by circle(L, 90) in


order to draw a square with arc.
Fibonacci spiral is generated when we repeat the square with arc drawing
over the square sizes following Fibonacci series. This means, we iterate the
abovesq_arc() function inside a loop that runs over the list of Fibonacci
numbers.

# Python Script: Fibonacci Spiral


L = [1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377]
# Pen to move without scratch penup()
# Starting point on Canvas goto(-150, 100)
# Pend down to draw pendown()
# Loop over L: Fibonacci Series for i in L:
sq_arc(i)

Fig. Fibonacci Spiral

Note: Fibonacci spiral, also known as called Golden spiral, is a fascinating


concept that spreads across Science and Culture, in architecture and painting,
through many centuries. The Golden spiral is abundant in natural objects
from arrangements of flower seed heads, patterns on shells to the formation
of hurricanes and to the spiral galaxies.

# Fibonacci spiral with colors


from numpy.random import uniform screensize(800, 500, bg = 'black')

title('Fibonacci Spiral') penup()


goto(-150, 100)
pendown()
width(3)
for i in L:
pencolor('yellow')
begin_fill()
fillcolor((uniform(0, 1), uniform(0, 1), uniform(0, 1))) sq_arc(i)
end_fill()

Fractals

A regular fractal is a geometrical object which is self-similar. That means, we


see same pattern in all length scales. We make a regular geometrical fractal
by repeating the same rule repeatedly. For this we employ a recursive
function. # Example of a recursive function

def fact(n):
if(n == 0): return 1 return n * fact(n - 1)

Note: The function fact() is defined to compute factorial of a number:��! =


�� ×(�� − 1)× … × 2 × 1. It is a repetitive act. In fact, we can think
of��! = �� ×(�� − 1)! = �� ×(�� − 1)×(�� − 2)!…and so on. So,
fact(n) = n × fact(n-1). The function fact() is a recursive function, as it is
itself called inside its definition. If we start from a number��, it is reduced
by 1 in every step untiln = 0.

# Koch Curve

Let us create a fractal by taking a line and applying some rule over it
repeatedly. Let the line be of length ��. Divide it into 3 equal parts:��/3.
Now take out the middle portion and replace it with two such segments which
are then joined to form an equilateral triangle. This task is repeated on all
segments through many generations. In the following, we show two
generations that evolved from a straight line on the left. [Named after
Swedish mathematician Helge von Koch.]
Fig. The
figures from left to right can be termed as Koch curves with order: 0, 1, 2.
Algorithm:

1. Take a line of length L as input.


2. The turtle moves forward by L/3.
3. Left turn by 60 deg, move forward by L/3.
4. Right turn by 120 deg, move fwd by L/3.
5. Left turn by 60 deg, move forward by L/3.
6. Repeat the steps2-5.

# Python recursive function: Koch curve

def koch(L, n):


“““
To draw a Koch curve. L = size, n = order
”””
if n == 0:

fd(L) # st line else:


koch(L/3, n-1) # Go 1/3
lt(60)
koch(L/3, n-1)
rt(120)
koch(L/3, n-1)
lt(60)
koch(L/3, n-1)

# Python Script: To Draw Koch curve

penup() # Lift the pen


goto(-250, 0) # To take the pen to pendown() # Down the pen to draw
width(2) # Width of line
koch(500, 4) # Koch Curve, order 4
hideturtle() # Hide turtle/ arrow

Fig. Koch curve


of order 4
Exercise: Make the Koch snowflake
[mathworld.wolfram.com/EquilateralTriangle.html].
# Fractal Tree

Take a straight line. Divide it into two parts: one goes left and the other goes
right. These are branches. Now each of the branches are similarly divided to
create more branches. This process continues up to some level. We form a
recursive function to create such a structure.

Algorithm:

1. Go forward by length: L
2. Turn left by 30 degree and go forward by a fixed fraction of L.
3. Turn right by 60 (30 + 30) degree and go forward by the fraction of L
4. Turn left by 30 degree and move backward by L.
5. Repeat the steps 2-4 until the length L reduces to L < 1.

# Python recursive function: Fractal tree def fractal_tree(L):


if L < 1:
return

else:
fd(L)
lt(30)
draw(L*f) # Branch on left rt(60)
draw(L*f) # Branch on right lt(30)
bk(L)

# Setting up turtle pos to start penup()


goto(0, -200)
pendown()
lt(90)

# Fraction of length to reduce f = 0.6


# Calling the recursive function fractal_tree(100)
Fig. Fractal tree

# Python Script: For colorful Fractal tree from


turtle import *
from numpy.random import uniform as u speed('fastest')
bgcolor('black')
f = 0.8
def draw(L):
if L < 1:

return
else:
fd(L)
color((u(0, 1), u(0, 1), u(0, 1)))
dot(5)
lt(45)
draw(L*f)
rt(90)
draw(L*f)
lt(45)
bk(L)
dot(5)

To save a figure
# For B&W
getcanvas().postscript(file =

"tree.eps") from PIL.Image import open im =


open('c:/Users/xx/tree.eps') im.save('tree.png')

# For Color

getcanvas().postscript(file = "tree.eps")
from PIL.Image import open
im = open('c:/Users/xx/tree.eps')
fig = im.convert('RGBA')
fig.save('tree.png')

Note:
• Create a postscript file (Here it is ‘tree.eps’) through getcanvas().

• Use the function calledopen() to open the file (with pathname) from Image
in PIL. Image is a module. PIL = Python Imaging Library.
• For B&W image: Save .eps file into a printable image file with save()
function. For Colored image: Convert the .eps file into a RGBA file (R =
Red, G = Green, B = Blue, A = Alpha). Then save the file. [Alpha for
opaque/transparent].

Python Computation with External Packages


296 | P y t h o n C o m p u t a t i o n w i t h E x t e r n a l P a c k a g e s

CHAPTER

8
NumPy: Numerical Python
“Number rules the universe”- Pythagoras

NumPy is Numerical Python. It is an external package (or Module, mostly


written in C) developed to work with core Python. NumPy consists of various
modules consisting of various classes, functions, and parameters. In short, we
make use of the NumPy library for our scientific and numerical
computations. Precompiled mathematical and numerical functions and
functionalities ensure great execution speed. Powerful data structures,
multidimensional arrays and vectorized functions enrich Python as a great
programming language.

If you are working on Python IDLE interpreter, NumPy (and other external
packages) needs to be installed over it. The procedure is discussed in
Appendix. Various Python distributions like Anaconda, Python (x, y) etc.
come with NumPyand some other common packages like SciPy and
Matplotlib.

Once NumPy is installed on the system, we import this on our namespace by


the same way we do for any module:import numpy.

8.1 NumPy Arrays

Array is a collection of elements like that of builtin object list. It is iterable. It


is mutable. However, arrays are quite different from lists in terms of
functionality.

# Import, dir, help


# Import with a local name >>> import numpy as np

# Check the directory >>> dir(np)


# Help on NumPy array
>>> help(np.array)

>>> type(np.array)
<class
'builtin_function_or_method'>

# Detailed help on array >>> np.lookfor(‘array’)


# Quick help
>>> np.info(‘array’)

Note: array() is a built-in function inNumPy. We need to always prefixnp to


refer (since we imported NumPy as local name np).

# Example

# Argument as list
>>> np.array([10, 20, 30]) array([10, 20, 30])

# Argument as tuple
>>> np.array((10, 20, 30)) array([10, 20, 30])

Note: The argument can be any iterable like list, tuple etc. Usually, we put a
list.
# Conversion of array to list >>> a = np.array([10, 20, 30])

# Converted to a list >>> a.tolist()


[10, 20, 30]

8.1.1 Built-in Functions on Arrays Functions and methods in __builtins__


in core Python apply on NumPy arrays.

>>> type(x) # Type of object <class 'numpy.ndarray'>


>>> len(x) # Length: No. of elements 3
>>> sum(x) # Sum of elements 60

Note: The type‘numpy.ndarray’ means ndimensional NumPy array. Arrays


of higher dimensions can be dealt with this function.

8.1.2 Methods and Attributes # As method: Size of array

>>> np.size(x)
3

# As attribute >>> x.size


3
# Dimension of array >>> np.ndim(x)
1
>>> x.ndim
1

# Shape of the array


>>> np.shape(x)
(3, ) ← a tuple (1D) >>> x.shape
(3,)

>>> x.dtype # Data Type dtype('int32')

# Size of each item in array >>> x.itemsize


4

Note: The default data type of the array isint32 = a 32-bit integer. The size of
each element in the array is 4 byte (= 32 bit). One remark on the above. The
methods np.size(), np.ndim() etc. are taken from NumPy whereas the
methods type(), len(), sum() are taken from built-in module in core Python,
all of these are basically functions.

8.1.3 Higher Dimensional Arrays # 2D array

>>> M = np.array([[1, 2, 3], [4, 5, 6]])


>>> M
array([[1, 2, 3],
[4, 5, 6]])
[The display is like a Matrix with 2
rows and 3 columns.]

>>> M.ndim
2
>>> M.size
6
>>> M.shape
(2, 3)
[Shape of the 2D array as a tuple]
Note: One could also write np.array([(1, 2, 3),(4, 5, 6)], the argument as list
of tuples or np.array(((1, 2, 3),(4, 5, 6))), the argument as tuple of tuples.

Axes
On a 2D array, we can go
along rows and columns.
If we go through different
rows (up-down) it is

along axis = 0 and


that through different columns (left-right) is axis = 1. We can refer elements
or apply methods on higher dimensional NumPy arrays through axes. # 3D
array
>>> D = np.array([[[1, 2], [3, 4]],

[[5, 6], [7, 8]]]) >>> D


array([[[1, 2],

[3, 4]],
[[5, 6],
[7, 8]]])
[Collection of two 2 × 2 arrays]

>>> D.ndim 3
>>> D.shape (2, 2, 2)

Note:
A 3D array is like a box (or container) which contains 2D arrays (or
matrices). The axes of a 3D array are shown in the adjacent

figure. The axis = 0, 1are along two directions on the shaded plane (which is
a 2D array) and the axis = 2 is perpendicular to that plane.

8.1.4 About Data Type


Data can be numbers or strings. The following data types are used in arrays.

• int8 (1 byte = 8-bit: Integer -128 to 127), int16 (-32768 to 32767), int32,
int64
• uint8 (unsigned integer: 0 to 255), uint16, uint32, uint64
• float16 (half precision float: sign bit, 5 bits exponent, 10 bits mantissa),
float32 (single precision: sign bit, 8 bits exponent, 10 bits mantissa), float64
(double precision: sign bit, 11 bit exponent, 52 bits mantissa)
• complex64 (complex number, represented by two 32-bit floats: real and
imaginary components), complex128 (complex number, represented by two
64-bit floats: real and imaginary components)
Acronyms:

Integers: i1 = int8, i2 = int16, i3 = int32, i4 = int64


Floats: f2 = float16, f4 = float32, f8 = float64
# Examples
# All integers
>>> x = np.array([10, 99, -6]) >>> x.dtype
dtype('int32')

# Integer, float mixed


>>> y = np.array([7.5, 99, -6]) >>> y
array([ 7.5, 99. , -6. ]) [Converted to all floats]

>>> y.dtype
dtype('float64')

# Integer, float, complex


>>> z = np.array([4, 1.5, 2+3j]) >>> z

array([4. +0.j, 1.5+0.j, 2. +3.j]) [Converted to all complex.]


>>> z.dtype
dtype('complex128')

# Array of String and number >>> s = np.array(['ab', '101', 99]) >>> s


array(['ab', '101', '99'],

dtype='<U3')
[Converted to all strings.]

Note:
• As it can be observed from above, the defaultdtype for integers is int32 (32
bit) and that for float is float64 (64 bit) while for complex it is complex128
(64 bits for real, 64 bits for image).

• Python always treats data in the broadest possible data type when it
encounters mixed data. It will treat all floats when floats and integers are
mixed. Complex, when complex and real numbers are mixed. Strings, when
numbers are mixed with strings. This is called upcasting.

• U3 is Unicode string of length 3. The defaultdtype is set by the maximum


size of the elements.

We can enforce data type as keyword argument inside the array() function.
>>> x = np.array([10, 99, -6], dtype = int)
>>> x.dtype
dtype('int32')
>>> x = np.array([10, 99, -6], dtype = float)
>>> x
array([10., 99., -6.])
>>> x.dtype
dtype('float64')

>>> x = np.array([10, 99, -6], dtype = complex) >>> x


array([10.+0.j, 99.+0.j, -6.+0.j]) >>> x.dtype
dtype('complex128')

>>> x = np.array([10, 99, -6], dtype = str)


>>> x
array(['10', '99', '-6'],
dtype='<U2')
>>> x.dtype
dtype('<U2')

# Datatype in more specific ways

>>> x = np.array([10, 99, -6], dtype = 'float16')


>>> x
array([10., 99., -6.],
dtype=float16)
>>> x = np.array([10, 99, -6], dtype = 'float32')
>>> x
array([10., 99., -6.],
dtype=float32)
>>> x = np.array([10, 99, -6], dtype = 'float64') >>> x
array([10., 99., -6.])

# Specific datatypes with acronyms

>>> x = np.array([10, 99, -6], dtype = 'f2')


>>> x
array([10., 99., -6.],
dtype=float16)
>>> x = np.array([10, 99, -6], dtype = 'f4')
>>> x
array([10., 99., -6.],
dtype=float32)
>>> x = np.array([10, 99, -6], dtype = 'f8')
>>> x
array([10., 99., -6.])

Note: In cases, where we declared datatype by dtype = ‘float64’ ordtype =


‘f8’, they did not appear in the display. This is because the datatype ‘float64’
is the default type. The same is true for complex or other datatypes, i.e.,
whenever it is not a default type, it will display to make us understand.

>>> x = np.array([10, 99, -6], dtype = 'complex64')


>>> x.dtype
dtype('complex64')

>>> s1 = np.array(['ab', '101', 99], dtype = 'S4')


>>> s1
array([b'ab', b'101', b'99'], dtype='|S4')
>>> s2 = np.array(['ab', '101abcd', 990188], dtype = 'S4')
>>> s2
array([b'ab', b'101a', b'9901'], dtype='|S4')
>>> s2.itemsize
4

Note: The string size is set to dtype =‘S4’ (strings cut down to size 4). Note
the effect of this on the string elements in the second array. In the display,
every string precedes with ‘b’ which stands for binary.

# Convert to a specific type with astype()


>>> a
array([2, 4, 6, 8])

# Cast into float type >>> a.astype('float') array([2., 4., 6., 8.])

# Complex type
>>> a.astype('complex')
array([2.+0.j, 4.+0.j, 6.+0.j,

8.+0.j]) >>> a
array([2, 4, 6, 8])
[Original array unchanged.]

8.1.5 Properties of Complex Arrays

8.1.5 Properties of Complex Arrays 1j])


>>> z.real # Real part

array([2., 5., 3.])


>>> z.imag # Imaginary part array([ 3., 2., -1.])
>>> z.conj() # Complex conjugate array([2.-3.j, 5.-2.j, 3.+1.j])

8.1.6 Methods for Creation of Arrays There are various methods in NumPy
to create arrays of numbers.
# 1 Range of numbers
np.arange(start, stop, step)

>>> help(np.arange)
……
arange(...)

arange([start,] stop[, step,], dtype=None)


Return evenly spaced values within a given interval.

Values are generated within the half-open interval ``[start, stop)``


……

Note: The array starts from ‘start’, proceeds in step of ‘step’ and stops before
‘stop’. We can read from the online manual and can understand how it works.
Notice that[start] and [stop] are written in square brackets while stop is not.
This means, the argument stop is only essential. If start andstop are not given,
they will assume default values (start = 0, step = 1).

# Examples
>>> np.arange(2, 10, 3) array([2, 5, 8])

# Default step = 1
>>> np.arange(2, 10)
array([2, 3, 4, 5, 6, 7, 8, 9])

# Default start = 0 >>> np.arange(5)


array([0, 1, 2, 3, 4])

>>> np.arange(0.2, 2, 0.4)


array([0.2, 0.6, 1. , 1.4, 1.8]) >>> np.arange(5, 10, 2, dtype= 'f')

array([5., 7., 9.], dtype=float32)

Note: Unlike the built-in method range() in core Python, this NumPy method
arange() can create fractional numbers or floats. Also, we can impose
datatype through keyword (Default datatype is None.).

# 2 Linear space
linspace(start, end, num)

>>> help(np.linspace)
Help on function linspace in module numpy:

linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None,


axis=0)

Return evenly spaced numbers over a specified interval.


……

Note: The method linspace()creates a linearly spaced array. The array starts
at ‘start’, ends at ‘end’ and ‘num’ is the number of elements to be created.

# Examples
>>> np.linspace(10, 20, 5)
array([10., 12.5, 15., 17.5, 20.])
Note: Step size = (20 – 10)/4 = 2.5 [4 gaps]

>>> np.linspace(10, 20, 5, endpoint = True)


array([10., 12.5, 15., 17.5, 20.])
>>> np.linspace(10, 20, 5, endpoint = False)
array([10., 12., 14., 16., 18.])

Note: The endpoint can be included or excluded throughthe keyword


‘endpoint’ with Boolean True/False. The default is endpoint = True. Note the
change in step size in two cases.

# To return Step size

>>> A = np.linspace(10, 20, 5, retstep = True) >>> A


(array([10. , 12.5, 15. , 17.5, 20.

]), 2.5) [Tuple of array and step size]

>>> x, step = A # Unpacking >>> x


array([10., 12.5, 15., 17.5, 20. ] >>> step # retstep = return step 2.5

# We can set datatype.

>>> np.linspace(10, 20, 5, retstep = True, dtype = int)


(array([10, 12, 15, 17, 20]), 2.5)

# 3 Logarithmic space
logspace(start, end, num)

>>> help(np.logspace)
Help on function logspace in module numpy:
logspace(start, stop, num=50, endpoint=True, base=10.0,

dtype=None, axis=0)
Return numbers spaced evenly on a log scale.……

# Examples: logscale
# default base = 10

>>> L1 = np.logspace(0.2, 1, 4) >>> L1


array([1.58489319, 2.92864456,
5.41169527, 10. ])
# With base = 2

>>> L2 = np.logspace(0.2, 1, 4, base = 2)


>>> L2
array([1.14869835, 1.38191288, 1.66247579, 2. ])

Note: The logspace() returns values in powers: values from100.2 to 101 for
L1 and 20.2 to 21 for L2. [Check,100.2=1.58489319 and so on.]

# Convert to linear scale


>>> np.log10(L1)
array([0.2 , 0.46666667,

0.73333333, 1. ]) >>> np.log2(L2)


array([0.2 , 0.46666667,

0.73333333, 1. ])

# Linear scale to compare


>>> np.linspace(0.2, 1, 4)
array([0.2 , 0.46666667,

0.73333333, 1. ])

Note: Since the numbers through logspace() returns values in powers of base.
The numbers are back in linear scale when we take log again.

8.1.7 Shape, Reshape, Size, Resize

An array can be reshaped into different dimensions. The rows and columns
for a 2D array are referred through axes:axis = 0, axis = 1 respectively. For
3D or any higher dimensional arrays, one can refer axes asaxis = 0, 1, 2 and
so on.

# shape, reshape

>>> A = np.array([2, 4, 6, 8, 10, 12])


>>> A.shape
(6,)
[1D array with 6 elements.]

# Converted to a 2D array ( 2 × 3) >>> B = A.reshape(2, 3)


>>> B
array([[ 2, 4, 6],

[ 8, 10, 12]])

# Reshaping into a 3 × 2 array >>> A.reshape(3, 2)


array([[ 2, 4],

[ 6, 8],
[10, 12]])
>>> A
array([ 2, 4, 6, 8, 10, 12]) [The original array unchanged.]

# size, resize >>> A.size

6
>>> B.size
6
>>> A.resize(2, 3)
>>> A
array([[ 2, 4, 6],
[ 8, 10, 12]])
[The original array modified.]

Note: The reshape() function returns a new array with a modified shape
whereas the resize()function modifies the array itself.

8.1.8 Flatten, Ravel


The method flatten() returns a copy of the array flattened into 1D array.

>>> x = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])


>>> x.flatten() # Returns 1D array
array([1, 2, 3, 4, 5, 6, 7, 8, 9])
# Flattens like that it does in C >>> x.flatten(order = 'C')
array([1, 2, 3, 4, 5, 6, 7, 8, 9])

# Flattens like that in Fortran >>> x.flatten(order = 'F')


array([1, 4, 7, 2, 5, 8, 3, 6, 9])

The method ravel() returns a flattened array.

>>> x.ravel() # Returns 1D array array([1, 2, 3, 4, 5, 6, 7, 8, 9]) >>>


x.ravel(order = 'C')
array([1, 2, 3, 4, 5, 6, 7, 8, 9]) >>> x.ravel(order = 'F')
array([1, 4, 7, 2, 5, 8, 3, 6, 9])

Note: There is a difference between flatten and ravel though they do the same
job.

• x.ravel() returns only reference/ view of the original array.


• As we modify the converted array, the original array is also modified.

>>> y = x.ravel()
>>> y
array([1, 2, 3, 4, 5, 6, 7, 8, 9])

>>> y[0] = 100


>>> y
array([100, 2, 3, 4, 5,

6, 7, 8, 9]) >>> x array([[100, 2, 3],

[ 4, 5, 6],
[ 7, 8, 9]])
[Original array changed.]

• On the other hand, x.flatten() returns a copy of the original array. So, if we
modify the converted array (flattened array), the original array is not affected.

>>> x = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])


>>> y = x.flatten()
>>> y[0] = 100
>>> y
array([100, 2, 3, 4, 5, 6, 7, 8, 9])
>>> x
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
[Original array unchanged.]

Note: ravel() is faster than flatten() and it does not occupy any extra memory.
flatten() is comparatively slower than ravel() as it occupies memory.

8.1.9 Unique Elements

>>> X = np.array([2, 0.5, 1, 2, 1, 0.5])


# Collect the unique elements
>>> np.unique(X)
array([0.5, 1. , 2. ])

>>> Y
array([[1, 0, 1],
[0, 0, 1],
[1, 1, 1]])
>>> np.unique(Y)
array([0, 1])
[Unique elements returned 1D array.]

>>> S = np.array(['a', 'b', 'b', 'c', 'a', 'b'])


>>> np.unique(S)

array(['a', 'b', 'c'], dtype = '<U1')


Application:

Suppose we have got some large array of some numbers. We want to check
what the unique numbers are there that the array is made of. [To create an
example, we generate random numbers with Normal distribution taken from
the module random in NumPy. This will be discussed towards the end of this
chapter. Numbers are then rounded off with zero decimal point to suit our
purpose.]
>>> x =
np.round(np.random.randn(100), 0) >>> x
array([ 1., 1., -1., -0., 0., 1.,
-1., -1., -1., 1., -2., 0.,

2., 0.,……… -1., -3., 1., -0.])


>>> np.unique(x)
array([-3., -2., -1., 0., 1., 2.])
8.1.10 Iterator on Array
Array is iterable. One can iterate over a NumPy array in a loop to print the
elements. For example,
# Iterating 1D array
>>> a = np.array([2, 5, 7, 9]) >>> for i in a:
print(i, end = ‘ ’)
2579
[end = ‘ ’ is to write side by side.]
# Iterating 2D array

>>> X = np.array([[1, 2, 3], [4, 5, 6]])


>>> for i in X:
print(i)

[1 2 3] [4 5 6] # Iterating 2D array with order for i in np.nditer(X, order =


'F'): print(i, end = ' ')
Output: 1 4 2 5 3 6
for i in np.nditer(X, order = 'C'): print(i, end = ' ')
Output: 1 2 3 4 5 6

Note: The iterator, np.nditer() iterates over an array to get the entries in a
sequence. The keyword argumentorder = ‘F’ means, it reads in FORTRAN
language style (reads column wise) and order = ‘C’ means, it reads in C
language style (reads row wise).

8.1.11 Indexing

Index or position of an element is done in the same way as that in list or


tuple. The index numbering starts as0, 1, 2… from left to right and -1, 2,
-3…. from right to left.
>>> x = np.array([ 2, 5, 8, 11, 14, 17, 21])
>>> x[0] # Element with index 0
2
>>> x[1]
5
>>> x[-1] # Last element
21
>>> x[-2]
17

8.1.12 Slicing

The slicing is done through position indices separated by colons [:]. For
example,
>>> x = np.array([ 2, 5, 8, 11,

14, 17, 21])

# start = 1, ends before 6, step = 2 >>> x[1:6:2]


array([ 5, 11, 17])

Note: In the above, the starting index = 1, stops before index = 6, step = 2. It
does not touch or cross the end index. In this example, x[1] = 3, x[3] = 11,
x[5] = 17.

# step = 1, by default >>> x[2:5]


array([ 8, 11, 14])

# From index 2 to end, step = 1 >>> x[2:]


array([ 8, 11, 14, 17, 21])

# From index 0 to 4, step = 1 >>> x[:5]


array([ 2, 5, 8, 11, 14])

# Sliced in full
>>> x[:]
array([ 2, 5, 8, 11, 14, 17, 21])
# Sliced in full
>>> x[::]
array([ 2, 5, 8, 11, 14, 17, 21])

Note:x[:] and x[::] are the same. Also, we can write, x[2:] in place ofx[2::].

# From index 0, stops before last index >>> x[:-1]


array([ 2, 5, 8, 11, 14, 17])

# Array reversed as step = -1 >>> x[::-1]


array([21, 17, 14, 11, 8, 5, 2])

# step = -2, from right to left >>> x[::-2]


array([21, 14, 8, 2])

# For higher dimensions

>>> X # 2D array array([[ 0, 1, 2, 3],


[ 3, 4, 5, 6],
[ 6, 7, 8, 9],
[ 9, 8, 7, 1]])
>>> X[2] # Row index 2 array([6, 7, 8, 9])
>>> X[-1] # Last row

array([ 9, 8, 7, 1])
>>> X[2:] # Slicing of rows array([[ 6, 7, 8, 9],

[ 9, 8, 7, 1]])
>>> X[:3]
array([[0, 1, 2, 3],

[3, 4, 5, 6],
[6, 7, 8, 9]])
>>> X[::2]
array([[0, 1, 2, 3],
[6, 7, 8, 9]])

>>> X[:]
array([[ 0, 1, 2, 3],
[ 3, 4, 5, 6],
[ 6, 7, 8, 9],
[ 9, 8, 7, 1]])
>>> X[:, 0] # Slicing of columns array([0, 3, 6, 9])
>>> X[:, -1] # The last column array([ 3, 6, 9, 1])

Note: The slicing of rows and columns in a 2D array are done with a set of
colons, separated by comma, X[::, ::]

For rows For columns


For example, X[:, 1:3] means, all the rows and 1 to 2 columns.
>>> X[:, 1:3] [[ 0, 1, 2, 3],array([[1, 2],
[ 3, 4, 5, 6],[4, 5], [ 6, 7, 8, 9],[7, 8], [ 9, 8, 7, 1]])[8, 7]])
>>> X[2:4, 1:3]
[[ 0, 1, 2, 3],
[ 3, 4, 5, 6],array([[7, 8], [ 6, 7, 8, 9],[8, 7]]) [ 9, 8, 7, 1]])
Note:X[::,1:3] and X[:,1:3] are the same.
8.1.13 Swap Two Elements
>>> a = np.array([10, 20, 30, 40,

50, 60]) >>> a[[1, 2]] = a[[2, 1]] >>> a


array([10, 30, 20, 40, 50, 60])

Interchanging rows and columns are part of some Matrix operations. As we


see, a 2D array can be treated as a matrix. [In the implementation of Gaussian
elimination (with partial pivoting), later in another chapter, we make use of
rowinterchange.]

# Interchange between two rows

>>> A
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])

# Interchange rows 1 & 2 >>> A[[1, 2]] = A[[2, 1]] >>> A


array([[1, 2, 3],
[7, 8, 9],
[4, 5, 6]])
# Interchange between two columns

>>> A
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])

# Interchange columns 1 & 2 >>> A[:, [1, 2]] = A[:, [2, 1]] >>> A
array([[1, 3, 2],

[4, 6, 5],
[7, 9, 8]])

8.2 NumPy Methods on Arrays


8.2.1 Checking All Elements

Test whether all array elements along a given direction evaluate toTrue. This
means, it is to check if all elements along a given axis is non-zero.

>>> x = np.array([1, 2, 3, 4, 5])


# All elements non-zero
>>> np.all(x)
True
>>> x = np.array([1, 2, 3, 0, 4])

# One element is zero >>> np.all(x)


False

>>> A
array([[3, 6, 5], [4, 7, 6],
[5, 0, 2],
[4, 1, 9]])

# Along axis = 1 (rows)


>>> np.all(A, 1)
array([ True, True, False, True])
# Along axis = 0 (columns) >>> np.all(A, 0)
array([ True, False, True])

8.2.2 Where is the Element?


It is often useful to search a particular element and to know where does it lie
in the array.

>>> x = np.array([2, 0, 1, -1, 10]) >>> np.where(x == -1)


(array([3], dtype=int64),)
[1st element in tuple is index.]

>>> A
array([[6, 6, 2, 2],
[5, 1, 5, 5],
[8, 0, 6, 0],
[6, 8, 5, 9]])
>>> np.where(A == 0)
(array([2, 2], dtype=int64), array([1, 3], dtype=int64))

Note: The last output indicates the occurrence of ‘0’ at two places.
8.2.3 Put/Insert New Element
To include new elements in arrays, we can do that in the following two ways.
# Put
>>> a = np.array([0, -1, 2, 5, 10])

# Replaces item at index 3


>>> np.put(a, 3, 99)
>>> a
array([0, -1, 2, 99, 10])
>>> a = np.array([0, -1, 2, 5, 10])

# As attribute
>>> a.put(3, 99)
>>> a
array([0, -1, 2, 99, 10]) [The array is changed.]

# Insert
>>> a = np.array([0, -1, 2, 5, 10])
# Inserts item at index 3 >>> np.insert(a, 3, 99)
array([0, -1, 2, 99, 5, 10]) >>> a
array([0, -1, 2, 5, 10]) [Original array unchanged.]

Note: The difference between two methods may be noted. The method
np.put() returns nothing. It alters the array itself. But inserting through
np.insert() returns a new array while keeping the old array unchanged.

# For higher dimensions


>>> A
array([[1, 2],
[3, 4]])
>>> np.insert(A, 1, [10, 12], axis

= 0) array([[ 1, 2],
[10, 12],
[ 3, 4]])

# axis = 0 means, along rows↓

>>> np.insert(A, 1, [10, 12], axis = 1)


array([[ 1, 10, 2],
[ 3, 12, 4]])

# axis = 1 means, along columns⟶

Note:
For the arrays of higher dimensions (2D or more), the axis reference is
important. For a 2D array (like a matrix), we usually refer through rows and
columns. In a 2D array, if we go up-down (along rows), we call itaxis = 0, if
we go left-right (along columns), we call itaxis = 1. For even higher
dimensional arrays, for example, for a 3D array, we refer another direction by
axis = 2. The axis reference (in all NumPy methods for higher dimensional
arrays) is done through the keyword:axis.

Without axis reference, the output will be a flattened array (1D array).

>>> A = np.array([[1, 2], [3, 4]]) >>> np.insert(A, 1, [10, 12]) array([ 1, 10,
12, 2, 3, 4]) >>> np.insert(A, 2, 10)
array([ 1, 2, 10, 3, 4])

8.2.4 Delete Element


# 1D array
>>> x = np.array([3, 5, 7, 9])

# Delete item at index 2


>>> np.delete(x, 2)
array([3, 5, 9])
>>> x
array([3, 5, 7, 9])
[Original array remains unchanged.]

Note:np.delete() returns a new array while original array remains unchanged.


# 2D array

>>> X
array([[ 1, 2, 3, 4], [ 5, 6, 7, 8],
[ 9, 10, 11, 12]])

# Row with index 2 deleted >>> np.delete(X, 2, axis = 0) array([[1, 2, 3, 4],


[5, 6, 7, 8]])

>>> X
array([[ 1, 2, 3, 4], [ 5, 6, 7, 8],
[ 9, 10, 11, 12]]) [Original array unchanged.]

# Column with index 2 deleted >>> np.delete(X, 2, axis = 1) array([[ 1, 2, 4],

[ 5, 6, 8],
[ 9, 10, 12]])

8.2.5 Split Arrays


Split an array into equal parts.
# For 1D array
>>> x = np.array([2, 3, 4, 5])
# Splitting into 4 parts
>>> np.split(x, 4)
[array([2]), array([3]),

array([4]), array([5])]
# Splitting into 2 equal parts
>>> np.split(x, 2)
[array([2, 3]), array([4, 5])]
# For 2D array

>>> X
array([[ 1, 2, 3, 4],
[ 5, 6, 7, 8],
[ 9, 10, 11, 12]])
>>> np.split(X, 3)
[array([[1, 2, 3, 4]]), array([[5, 6, 7, 8]]), array([[ 9, 10, 11, 12]])]

# Default axis = 0 ↓

>>> np.split(X, 3, axis = 0)


[array([[1, 2, 3, 4]]), array([[5, 6, 7, 8]]), array([[ 9, 10, 11, 12]])]

>>> np.split(X, 4, axis = 1)


[array([[1],[5],[9]]), array([[ 2],[ 6],[10]]), array([[ 3],[ 7], [11]]), array([[ 4],[
8],[12]])]

>>> np.split(X, 2, axis = 1) [array([[ 1, 2],[ 5, 6],[ 9, 10]]), array([[ 3, 4],[ 7,


8],[11, 12]])]

Note: The output is a list of arrays resulting from splitting. Each array is of
the same dimension of the original array.

Axis reference is important for any NumPy method applying to higher


dimensional arrays. So, axis reference is a must for splitting of higher
dimensional arrays too. However, we do not require axis reference when we
use the methods, vsplit(X, 3) or hsplit(X, 4), for splitting in vertical and
horizontal directions respectively.
# Split vertically

>>> np.vsplit(X, 3)
[array([[1, 2, 3, 4]]), array([[5, 6, 7, 8]]), array([[ 9, 10, 11, 12]])]
# Split horizontally

>>> np.hsplit(A, 4)
[array([[1],
[5],
[9]]), array([[ 2], [ 6],
[10]]), array([[ 3], [ 7],
[11]]), array([[ 4], [ 8],
[12]])]

8.2.6 Append Values


# For 1D array
>>> x = np.array([1, 3, 5, 7])

# Number 9 to be appended at end >>> new = np.append(x, 9)


>>> new
array([1, 3, 5, 7, 9])
>>> x
array([1, 3, 5, 7])
[Original array remains unchanged.]

>>> y = np.array([10, 20]) >>> np.append(x, y)


array([ 1, 3, 5, 7, 10, 20]) [Two 1D arrays appended.]

# For 2D array (with axis reference) >>> X


array([[1, 2],
[3, 4]])
>>> np.append(X, [[10, 12]], axis

= 0) ↓ array([[ 1, 2],
[ 3, 4],
[10, 12]])

>>> np.append(X, [[10], [12]], axis = 1) ⟶ array([[ 1, 2, 10],


[ 3, 4, 12]])

Note: The arrayX remains unchanged. A new array is created. When axis is
specified, the value (to be appended) must have the correct shape. Otherwise,
there will be error. Without axis reference, no matter what the shape of the
value to be added is, it returns always a flattened array.

# No axis ref, Flattened array

>>> np.append(X, [10, 12]) array([ 1, 2, 3, 4, 10, 12]) >>> np.append(X,


[[10], [12]]) array([ 1, 2, 3, 4, 10, 12])

8.2.7 Concatenate

Join a sequence of arrays along an existing axis. Unlike that of list or tuple,
NumPy arrays cannot be concatenated with mathematical operator (+).

# For 1D arrays

>>> a = np.array([10, 20, 30]) >>> b = np.array([40, 50, 60]) >>>


np.concatenate((a, b))
array([10, 20, 30, 40, 50, 60])

# For 2D arrays >>> x


array([[1, 2],

[3, 4]])
>>> y
array([[5, 6],

[7, 8]])
>>> np.concatenate((x, y), axis = 1) array([[1, 2, 5, 6],
[3, 4, 7, 8]])
>>> np.concatenate((x, y), axis =

0) array([[1, 2],
[3, 4],
[5, 6],
[7, 8]])
# By default axis = 0 >>> np.concatenate((x, y)) array([[1, 2],

[3, 4],
[5, 6],
[7, 8]])

8.2.8 Stacking Arrays


# Stacking along axis = 0 (vertical)

>>> x
array([[1, 2],

[3, 4]]) >>> y


array([[5, 6],

[7, 8]])

# Default axis = 0
>>> z = np.stack((x, y)) >>> z
array([[[1, 2],

[3, 4]],
[[5, 6], [7, 8]]])
# Stacking along axis = 1 (horizontal)

>>> z = np.stack((x, y), axis = 1) >>> z


array([[[1, 2],

[5, 6]],
[[3, 4], [7, 8]]])

Note: The argument in stack() is to be tuple or list. The resulting array


converts to higher dimension. It may be understood that we stack 2D planes
to make a 3D Box.

>>> x.shape # 2D (2, 2)


>>> y.shape # 2D (2, 2)
>>> z.shape
(2, 2, 2)
[3D after stacking.]

# Stacking without shape change

# Vertical stack
>>> np.vstack((x, y))
array([[1, 2],
[3, 4], [5, 6], [7, 8]])

# Horizontal stack >>> np.hstack((x, y)) array([[1, 2, 5, 6],

[3, 4, 7, 8]])

Note: Thevstack() and hstack() do not lead to any shape change (no
conversion to higher dim.). These are equivalent to concatenation along the
axes. For stacking, the objects must match in shape in the direction of
stacking. Otherwise, it will raise errors. With vstack orhstack, we increase the
size of an array but not dimension.

>>> z = np.array([10, 12]) >>> np.vstack((x, z))


array([[ 1, 2],

[ 3, 4],
[10, 12]])
>>> z = np.array([[10],

[12]]) >>> np.hstack((x, z)) array([[ 1, 2, 10],

[ 3, 4, 12]])

8.2.9 Methods of Statistics


# Sum of elements

>>> x = np.array([10, 20, 30, 40, 50, 60, 70, 80, 90])
>>> np.sum(x)
450
>>> x.sum()
450
# Cumulative Sum

>>> np.cumsum(x) array([10, 30, 60, 100, 150, 210, 280, 360, 450],
dtype=int32)

# Sum of elements
# As attribute
# Cumulative Sum

# Minimum and Maximum >>> x.min() # Minimum

10
>>> x.max() # Maximum
90

For 2D arrays (With axis ref.) # Reshaped x into a 2D array

>>> y = x.reshape(3, 3) >>> y


array([[10, 20, 30],

[40, 50, 60],


[70, 80, 90]])

# Sum of each column >>> np.sum(y, axis = 0) array([120, 150, 180])

# Sum of each row


>>> np.sum(y, axis = 1) array([ 60, 150, 240])

# Methods as attribute

>>> y.sum(0) # Along axis = 0


array([120, 150, 180])
>>> y.sum(1) # Along axis = 1
array([ 60, 150, 240])
>>> y.sum() # No axis ref. 450
[Sum of all elements.]

Note: The axis reference is important for any NumPy method to be applied
over higher dimensional NumPy arrays.

# Mean, Median

>>> np.mean(y, axis = 0) array([40., 50., 60.])


[Also, y.mean(0) as attribute.]

>>> np.mean(y, 1)
array([20., 50., 80.]) >>> np.mean(y)
50.0
>>> np.median(y, 0) array([40., 50., 60.]) >>> np.median(y, 1) array([20.,
50., 80.]) >>> np.median(y)
50.0

Note: One can use the method, np.average() instead ofnp.mean(). The axis
reference is the same for all the methods. Also, note that ‘median’ cannot be
written as NumPy attribute. Application:

The np.mean() over a multidimensional array is frequently used in Machine


Learning (ML) algorithm. Imagine, a cluster of points where we need to find
a central position out of them. A cluster of three points can correspond to the
vertices of a triangle. The (x, y) coordinates of the vertices can be represented
by rows of a 2D NumPy array. The centroid of the triangle can be found by
taking np.mean() along axis = 0 [That means, coming down through rows:
mean of x-values, mean of y-values.].

>>> points = np.array([[1, 1], [2, 3], [3, 1]])


>>> centroid = np.mean(points, axis = 0)
>>> centroid
array([2. , 1.66666667])

Fig. 1 The triangle with vertices: (1, 1), (2, 3), (3, 1)
The centroid is also shown at (2, 1.666 …). [The plotting can be done with
Polygon(points) or with plot(x, y) function frommatplotlib.pyplot module.]

# Variance, Standard deviation, Peak-to-Peak

>>> x = np.array([0, 1, -1, 2, 3,


-2, 0.5, 1.5, 2.5])
>>> x.var()
2.388888888888889
>>> x.std()
1.5456030825826172

# Peak-to-Peak value: Highest - Lowest >>> x.ptp()


5.0

>>> X = x.reshape(3, 3)
>>> X
array([[ 0. , 1. , -1. ],

[ 2. , 3. , -2. ],
[ 0.5, 1.5, 2.5]])
>>> X.var(0) # Along axis = 0 array([0.72222222, 0.72222222,
3.72222222]) >>> X.var(1) # Along axis = 1 array([0.66666667,
4.66666667, 0.66666667])

>>> X.std(0)
array([0.84983659, 0.84983659,

1.92930615]) >>> X.std(1)


array([0.81649658, 2.1602469 ,

0.81649658])

>>> X.ptp(0)
array([2. , 2. , 4.5]) >>> X.ptp(1)
array([2., 5., 2.]) # Cov, Correlation Coefficient

>>> x = np.random.uniform(0, 1, 1000)


>>> y = np.random.uniform(0, 1, 1000)
# Covariant Matrix
>>> np.cov(x, y)
array([[0.08178297, 0.00351387], [0.00351387, 0.083107 ]])

# Correlation Coefficient Matrix >>> np.corrcoef(x, y)


array([[1., 0.04262211],

[0.04262211, 1. ]])

Note: Two sets of 1000 random data points are taken. Correlation and
Covariance are seen among them. The Correlation Coefficient Matrix is
calculated with Pearson product-moment correlation coefficients between
two sets of data.

8.2.10 Product, Difference


>>> x = np.array([1, 2, -1, 3, -2])

# Product of elements >>> np.prod(x)


12

# Essentially, factorial of 5 >>> np.prod(range(1, 6)) 120

# Diff between successive elements >>> np.diff(x)


array([1, -3, 4, -5])

Note: The difference array can be obtained through slicing also.

>>> x = np.array([1, 2, -1, 3, -2])


>>> x[1:] - x[:-1]
array([ 1, -3, 4, -5])

Note: For multidimensional arrays, the axis reference in prod() ordiff() is


similar to that of any other NumPy methods. The same is true for other
methods like np.add() and no.divide()for addition and divisions. NumPy
arrays are vectorized. Element wise mathematical operations between NumPy
arrays can be done with mathematical operators like +, -, *, /, ** and so on.
But the NumPy methods are particularly useful because we can give axis
reference and manipulate through other keyword arguments.

Application:

Consider the following �� vs. �� data [A sigmoidal curve created


through NumPy function and array, for demonstration.]. The first derivative,
�� = ����/���� is computed through np.diff().

# Python Script

x = np.linspace(-5, 5, 100) y = 1.0/(1 + np.exp(-x)) z = np.diff(y)/np.diff(x)

# For plotting
import matplotlib.pyplot as plt
plt.subplot(211) plt.plot(x, y)

plt.subplot(212)
plt.plot(x[:-1], z)
plt.show()
Fig. 2 The upper figure is for x-y curve and the plot below is the x-z curve
(derivative). Note that the maximum in the second curve occurs at the
inflexion point which is expected.

Note: In the above Python script, the slicing x[:-1] is done (while plotting the
figure at the bottom) becausenp.diff() generates an array whose length is one
less than the original. [Details about the plotting style, subplot etc. can be
found in Chapter 9 onMatplotlib.]

Exercise:
The position of a particle is given by the formula:

�� = 1/(1 + ��−2(��−4)), where time �� is measured in equal intervals


between 0 and 10 units of time. Find the velocity of the particle in this time
interval. Plot the velocity profile with time. Also, calculate the acceleration of
the particle and plot it with time.
[Guideline: While calculating the array of time and the time difference (step
size), you may use
>>> t, dt = np.linspace(0, 10, 100, retstep = True)
Velocity, �� = ∆��/∆��, acceleration, �� = ∆2��=(∆��)2 ∆(∆��)
with ∆�� = np.diff(x), ∆2�� =(∆��)2
np.diff(np.diff(x)). While plotting �� vs.
��, you may considert[:-1]. To plot�� vs. ��, we may consider
t[1:-1].]
8.2.11 Trace

Trace of a 2D array or matrix is the sum of its diagonal elements. We can


give offset reference to pick some diagonal (Main diagonal or any off
diagonal on the up and down directions).

>>> A
array([[-1, -4, -2, 6], [-8, 3, 6, -4],
[-6, -4, 4, -6],
[ 9, -1, 3, -9]])

# Sum of elements on main diagonal >>> np.trace(A)


-3

# On 1st upper off-diagonal >>> np.trace(A, offset = 1)


-4
# On 2nd upper off-diagonal >>> np.trace(A, 2)
-6

# On 1st lower off-diagonal >>> np.trace(A, -1)


-9

# On 2nd lower off-diagonal >>> np.trace(A, -2)


-7

8.2.12 Sorting
>>> x = [-2, 0, 3, 1, 10, 5, -7]

# Ascending order
>>> np.sort(x)
array([-7, -2, 0, 1, 3, 5, 10])

# Descending order
>>> np.sort(x)[::-1]
array([10, 5, 3, 1, 0, -2, -7])

Note: The NumPy method sort() creates a new sorted list (or array) in
ascending order. Original list remains unchanged. To get a sorted list in
descending order, we used the NumPy slicing method to reverse the sorted
list (which resulted in ascending order).

# For higher dimensional arrays

>>> L
array([[-6, -4, 7, 5, 6], [ 0, 9, 1, 4, -1],
[ 4, 8, 5, 0, 2],
[ 8, 1, -2, 3, 6]])

# Sorting columns, ascending >>> np.sort(L, 0)


array([[-6, -4, -2, 0, -1],

[ 0, 1, 1, 3, 2], [ 4, 8, 5, 4, 6], [ 8, 9, 7, 5, 6]])

# Sorting rows, ascending >>> np.sort(L, 1)


array([[-6, -4, 5, 6, 7], [-1, 0, 1, 4, 9], [ 0, 2, 4, 5, 8], [-2, 1, 3, 6, 8]])

Note: np.sort(L) # Default axis = 1


# Sorting data through a column

In the following, three columns (x, y, z) data is represented by a 2D array.


Our task is to sort the data/array through a column while keeping the
corresponding entries in the other columns to reposition accordingly. This
means, the rows will be sorted according to a column entry. The NumPy
method is argsort().

>>> x
array([[ 1, 5, 20],
[ 5, 7, 64],
[ 3, 9, 97],
[ 2, 4, 38],
[ 4, 6, 90]])
>>> x[:, 0] # First Column array([1, 5, 3, 2, 4])
# Sorting of indices
>>> ind = np.argsort(x[:, 0]) >>> ind
array([0, 3, 2, 4, 1], dtype=int64)

# Sorting rows through 1st column >>> x[ind]


array([[ 1, 5, 20],

[ 2, 4, 38],
[ 3, 9, 97],
[ 4, 6, 90],
[ 5, 7, 64]])

# Sorting rows through 2nd column >>> x[np.argsort(x[:, 1])] array([[ 2, 4,


38],

[ 1, 5, 20],
[ 4, 6, 90],
[ 5, 7, 64],
[ 3, 9, 97]])

The method argsort() returns the indices that would sort an array.
>>> L = np.array([10, 8, 12])

# Position indices that would sort >>> ind = np.argsort(L)


>>> ind
array([1, 0, 2], dtype=int64)

# Sorted through index positions >>> L[ind]


array([8, 10, 12])

8.3 Arithmetic Operations

Mathematical operations between two arrays correspond to that between


corresponding elements of the arrays.
>>> x = np.array([1, 2, 3, 4]) >>> y = np.array([5, 6, 7, 8]) >>> x + y
array([ 6, 8, 10, 12])
>>> x - y
array([-4, -4, -4, -4])
>>> x * y
array([ 5, 12, 21, 32])

>>> x/y
array([0.2 , 0.33333333,

0.42857143, 0.5 ]) >>> x**y


array([ 1, 64, 2187, 65536],

dtype=int32) >>> x**2 + y**3


array([126, 220, 352, 528],

dtype=int32)

The operations between higher dimensional arrays are carried out in the same
way. Addition of two 2D arrays can be considered as mathematical Matrix
addition.

# Matrix Addition
>>> A
array([[1, 2],

[3, 4]])
>>> B
array([[5, 6],

[7, 8]])
>>> A + B # Matrix Addition array([[ 6, 8],

[10, 12]])
>>> np.add(A, B) # By NumPy method array([[ 6, 8],

[10, 12]])

Note: We can do operations between NumPy arrays and other iterables. The
items of the iterable will have operations with the corresponding elements of
the array. For example,

# The list added with every row >>> A + [10, 11]


array([[11, 13],

[13, 15]])

# Tuple added with every row >>> A + (10, 11)


array([[11, 13],

[13, 15]])
NumPy arrays of various shapes (or an array and a scalar) can be added
through broadcasting.
8.4 Broadcasting

Broadcasting refers to the ability of NumPy to treat arrays of different shapes


during arithmetic operations. Arithmetic operations on arrays are usually
done between corresponding elements.

If two arrays are of the same shape, then these operations are smoothly
performed. But if the dimensions of two arrays are dissimilar, elementto-
element operations are not possible. However, operations on arrays of
dissimilar shapes are still possible in NumPy, because of the broadcasting
capability. The smaller array is to broadcast to the size of the larger array so
that they have compatible shapes. Here are a few examples.

>>> x
array([[1, 2, 3], [4, 5, 6],
[7, 8, 9]]) >>> y = 10
>>> x + y
array([[11, 12, 13], [14, 15, 16],
[17, 18, 19]])
>>> x
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])

>>> y = np.array([10, 11, 12]) >>> x + y


array([[11, 13, 15],

[14, 16, 18],


[17, 19, 21]]

>>> x = np.array([[1, 2, 3], [4, 5, 6],


[7, 8, 9]]) >>> y = np.array([[10], [11],

[12]]) >>> y
array([[10],

[11],
[12]])
>>> x + y
array([[11, 12, 13],
[15, 16, 17],
[19, 20, 21]])

8.5 Transpose, Flip, Rotate

# Transpose
>>> M
array([[1, 2, 3],

[4, 5, 6]])
# Row-Column interchange
>>> M.T

array([[1, 4],
[2, 5],
[3, 6]])

[Attribute: Transpose of array M] >>> M.transpose()


array([[1, 4],

[2, 5],
[3, 6]])
[By method: Transpose of array]

# Flipping
# Flipping left-right
>>> np.fliplr(M)
array([[3, 2, 1], [6, 5, 4]])

# Flipping up-down >>> np.flipud(M) array([[4, 5, 6],

[1, 2, 3]])
# Rotating
Rotate an array by 90 degrees in the plane specified by axes.

>>> np.rot90(M, axes = (0, 1)) array([[3, 6],


[2, 5],
[1, 4]])
>>> np.rot90(M, axes = (1, 0)) array([[4, 1],
[5, 2],
[6, 3]])

# Rotation by 90 deg, default axis >>> np.rot90(M)


array([[3, 6],

[2, 5],
[1, 4]])
8.6 Matrix Object
The object np.matrix returns a matrix from an array-like object from a string
of data.

>>> A = np.matrix([[1, 2, 3], [4, 5, 6]])


>>> type(A)
<class 'numpy.matrix'>
>>> A

matrix([[1, 2, 3],
[4, 5, 6]])
>>> B = np.matrix([[1, 2], [3, 4], [5, 6]])
>>> B
matrix([[1, 2],
[3, 4],
[5, 6]])
# Matrix Product

# Product of two Matrices >>> A*B


matrix([[22, 28],

[49, 64]])
8.6.1 Complex Matrix

Linear Algebra with Complex Matrices is important for many areas in


Physics, especially in Quantum Mechanics where we deal with Hermitian and
Unitary matrices.

# A Complex Matrix
>>> C
matrix([[1.+2.j, 0.+1.j], [2.-3.j, 0.+4.j]])

>>> C.T # Transpose matrix([[1.+2.j, 2.-3.j],


[0.+1.j, 0.+4.j]])

# Conjugate Matrix
>>> C.conjugate()
matrix([[1.-2.j, 0.-1.j],

[2.+3.j, 0.-4.j]])

# Adjoint (Conjugate transpose) >>> C.H


matrix([[1.-2.j, 2.+3.j],

[0.-1.j, 0.-4.j]])
# Conditional statement inside NumPy array

>>> X
array([[ 0. , 0.5, 1. ], [ 2. , 3. , 4. ],
[10. , -3. , 5. ]])

# Returns flattened array


>>> X[X > 3]
array([ 4., 10., 5.])
8.7 Special Arrays
# Uninitialized array of specific shape

>>> np.empty(3)
array([8.48798316e-314,
1.69759663e-313, 2.54639495e-313]) >>> np.empty((2, 2), dtype =

'float') array([[5.e-324, 5.e-324],


[0.e+000, 0.e+000]])
# Fill array with a scalar value >>> A = np.array([1, 2, 3])

# Fill with 0 in the shape of A. >>> A.fill(0)


>>> A
array([0, 0, 0])
# Identity (Identity Matrix)

>>> np.identity(4) # of order 4 array([[1., 0., 0., 0.],


[0., 1., 0., 0.],
[0., 0., 1., 0.],
[0., 0., 0., 1.]])

# Identity Matrix of order 4 >>> np.eye(4)


array([[1., 0., 0., 0.],

[0., 1., 0., 0.], [0., 0., 1., 0.], [0., 0., 0., 1.]])

# On first upper off-diagonal >>> np.eye(4, k = 1)


array([[0., 1., 0., 0.],

[0., 0., 1., 0.], [0., 0., 0., 1.], [0., 0., 0., 0.]])

# On first lower off-diagonal >>> np.eye(4, k = -1)


array([[0., 0., 0., 0.], [1., 0., 0., 0.], [0., 1., 0., 0.], [0., 0., 1., 0.]])

# Array with 1’s

# 1D array with all 1 ’s >>> np.ones(3)


array([1., 1., 1.])

# 2D array with all 1 ’s >>> np.ones((3, 4))


array([[1., 1., 1., 1.],

[1., 1., 1., 1.], [1., 1., 1., 1.]])


# Array with all 0’s (Null Matrix)

>>> np.zeros(3)
array([0., 0., 0.])
>>> np.zeros((3, 3)) array([[0., 0., 0.],

[0., 0., 0.],


[0., 0., 0.]])
# 0’s put in the shape of an array >>> A
array([[1, 2],
[3, 4]])

# Like the shape of A >>> np.zeros_like(A) array([[0, 0],

[0, 0]])
# Full array (fill array with a number)

# Full Matrix
>>> np.full((3, 3), 5) array([[5, 5, 5],

[5, 5, 5],
[5, 5, 5]])
# Follow the shape of an array, fill with a number.

>>> A
array([[1, 2], [3, 4], [5, 6]])

# Like the shape of A >>> np.full_like(A, 10) array([[10, 10],

[10, 10],
[10, 10]])
# Diagonal array

# Main diagonal
>>> np.diag((1, 2, 3)) array([[1, 0, 0],
[0, 2, 0],
[0, 0, 3]])

# First off-diagonal (upper) >>> np.diag((1, 2 ,3), k = 1) array([[0, 1, 0, 0],

[0, 0, 2, 0],
[0, 0, 0, 3],
[0, 0, 0, 0]])
# Second off-diagonal (lower) >>> np.diag([1, 2, 3], k = -2) array([[0, 0, 0,
0, 0],

[0, 0, 0, 0, 0],
[1, 0, 0, 0, 0],
[0, 2, 0, 0, 0],
[0, 0, 3, 0, 0]])

Note: The sizes (orders) of the created arrays are automatically determined
by the argument. The length of the argument array is the length of the
diagonal where it fits in.

# Follow the shape of an array, fill with a number

>>> A
array([[1, 2, 3], [4, 5, 6],
[7, 8, 9]])

# Diagonal of A is filled with 0 >>> np.fill_diagonal(A, 0) >>> A

array([[ 0, 2, 3], [4, 0, 6], [7, 8, 0]])

# Repeat elements of an array # Repeat 5 in 8 times

>>> np.repeat(5, 8)
array([5, 5, 5, 5, 5, 5, 5, 5])

# Repeat array [1, 2], 4 times >>> np.repeat([1, 2], 4)


array([1, 1, 1, 1, 2, 2, 2, 2])

>>> np.repeat([[1, 2], [3, 4]], 3) array([1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4])


Application:
>>> x = np.repeat([0, 1, 0], 100) >>> plt.plot(x)

Fig. 3 Square waveform generated through np.repeat().


# Repeat elements along some axis

>>> np.repeat([[1, 2], [3, 4]], 3, axis = 0)


array([[1, 2],
[1, 2],
[1, 2],
[3, 4],
[3, 4],
[3, 4]])
>>> np.repeat([[1, 2], [3, 4]], 3, axis = 1)
array([[1, 1, 1, 2, 2, 2], [3, 3, 3, 4, 4, 4]])

8.8 Products of Arrays


# Inner product

>>> u = np.array([1, 2, 3]) >>> v = np.array([-1, 0, 1]) >>> np.inner(u, v)


2

# Cross Product
>>> np.cross(u, v) array([ 2, -4, 2])

Note: The above methods can be treated asinner product (scalar product) and
cross product (vector product) between two vectors u and v.

Inner product: u.v = ∑���� ������ = 1 ×(−1)+ 2 × 0 + 3 × 1 = 2


Cross Product:
�� �� ��
u×v = | 1 2 3| = ��(2)+ ��(−4)+ ��(2) −1 0 1

Application:
Volume of a parallelepiped whose three sides are given by three vectors:
��⃗= 2��̂ − 3��̂, ��⃗⃗= ��̂ + ��̂ − ��̂, ��⃗= 3��̂ − ��̂

Volume =��⃗.(��⃗⃗× ��⃗)= 4 (in arbitrary unit)


# Python Script

import numpy as np
A = np.array([2, -3, 0])
B = np.array([1, 1, -1])
C = np.array([3, 0, -1])
vol = np.inner(A, np.cross(B, C)) print(vol)

8.8.1 Product of Multi-Dimensional Arrays


# Inner product between 2D array and scalar

>>> np.inner(np.eye(3), 5)) array([[5., 0., 0.],


[0., 5., 0.],
[0., 0., 5.]])
# Product between two 2D arrays >>> A
array([[1, 2],

[3, 4]])
>>> B
array([[5, 6],

[7, 8]])
>>> np.inner(A, B)
array([[17, 23],
[39, 53]])
[Product between respective elements]

Note: Inner product between higher dimensional arrays is the sum of product
of corresponding elements on the axes. For example, in the above,

1 × 5 + 6 × 2 = 17, 1 × 7 + 2 × 8 = 23, 3 × 5 + 4 × 6 = 39, 3 × 7 + 4 × 8 = 53.


8.8.2 Matrix like Product
>>> A = np.array([[1, 2], [3, 4]]) >>> B = np.array([[5, 6], [7, 8]])

# AB (actual matrix product) >>> np.dot(A, B)


array([[19, 22],

[43, 50]])
>>> np.dot(B, A) # BA array([[23, 34],

[31, 46]])
Note: We can also write as attributes, A.dot(B), B.dot(A).
# Product using special symbol >>> A@B # Matrix like Product
array([[19, 22], [43, 50]])

Note: The inner product by np.inner() and matrix product by np.dot()


between two vectors (1D arrays) are the same.

# Vector dot product

>>> u = np.array([1, 2, 3]) >>> v = np.array([-1, 0, 1]) >>> np.vdot(u, v)


2
Note: The vector dot product is the sum of the product of the corresponding
elements. For the above example, 1 ×(−1)+ 2 × 0 + 3 × 1 = 2.

# Vector dot product for higher dimension

>>> A = np.array([[1, 2], [3, 4]]) >>> B = np.array([[5, 6], [7, 8]]) >>>
np.vdot(A, B)
70

Note: The method np.vdot() always results in a scalar. In higher dimension,


first the arrays are flattened as a vector and then the inner product rule (or
vector dot product) is applied.

For the above example, 1 × 5 + 2 × 6 + 3 × 7 + 4 × 8 = 70 (Elementwise


operation).
8.8.3 Tensor Operations

Tensors can be represented as multidimensional arrays (ndarray) in NumPy.


The elements of a mathematical Tensor:

��111 ��121 ��131 ��112 ��122 ��132


�� = (��211 ��221 ��231 ��212 ��222 ��232) ��311 ��321
��331 ��312 ��322 ��332
In a slightly different notation, one can write,
�� 111 ��121 ��131

�� =[[��211 ��221 ��231],


��311 ��321 ��331

�� 112 ��122 ��132


[��212 ��222 ��232] ] ��312 ��322 ��332

So, we can represent this as an array of two matrices, or a 3D array in


NumPy. As an example, let us create a tensor T as a (2 × 3 × 3) array:

>>> import numpy as np


>>> T = np.array([[[1, 2, 3], [4, 5, 6], [7, 8, 9]],[[10, 11, 12], [13, 14, 15],
[16, 17, 18]]]) >>> T
array([[[ 1, 2, 3],

[ 4, 5, 6],
[ 7, 8, 9]],

[[10, 11, 12], [13, 14, 15], [16, 17, 18]]])

>>> T.shape
(2, 3, 3)

Note: Addition and Subtraction between two Tensors are done in the same
way as that between two mathematical matrices (or 2D NumPy arrays). If
there are two tensors �� and �� of same shape, the addition (or
subtraction) is,�� = �� + ��, where �� is a new Tensor of the same
shape.

The element-wise product between two Tensors of the same dimension will
result in a new Tensor with same dimension. Such product is called
Hadamard product and it is symbolically written as�� = �� ∘ ��. In
NumPy, if�� and �� are arrays (3D arrays) then the Hadamard product is
the usual elementwise product between two arrays, written as�� = �� ∗
�� [The same is true for Tensor divisions also.].

Tensor Product

Tensor product is quite different than the Hadamard product. If two


Tensors�� and �� have dimensions �� and �� then the resulting
tensor product, �� = �� ⨂ �� will have dimension (�� + ��).
Note that the tensor product C will consist of the elements where each
element of A will multiply the Tensor B. For example,

�� = (��
11 ��12
��
21
��
22
), �� = (��11 ��12),
��21 ��22
�� = �� ⨂ �� = (��11∗ �� ��12∗ ��
=
��21∗ �� ��22∗ ��) ��
(
∗ (��11 ��12) ��12∗ (��11 ��12)11 ��21 ��22 ��21 ��22 ) ��21∗
(��11 ��12) ��22∗ (��11 ��12)
��21 ��22 ��21 ��22
Note that�� is represented as a 3D array in NumPy style (each element a
matrix):
��12
[��11∗ (��11 )]
��21 ��22
11 ��12
[�� ∗ (�� )]12 ��21 ��22
11 ��12
[�� ∗ (�� )]21 ��21 ��22
[
[��
22
��
∗ (��11 ��12)]]21 ��22

Note that a vector is a tensor of order 1 and a matrix is a tensor of order 2


[Scalar is a tensor of order 0.]. The tensor product is implemented in NumPy
with tensordot() function. For usual tensor product, the axis must be set to 0.

>>> A = np.array([1, 2, 3]) >>> B = np.array([4, 5])


>>> np.tensordot(A, B, axes = 0)

array([[ 4, 5], [ 8, 10], [12, 15]])

Note: A and B are tensors of rank 1 and the product is a tensor of rank 2.
Explanation:(1 2 3)⨂(4 5)=

1 × 4 1 × 5 4 5 (2 × 4 2 × 5) = (8 10)
3 × 4 3 × 5 12 15

It is like a fully connected network of two layers (often considered in Neural


network).
>>> A = np.array([[1, 2, 3], [4, 5, 6]])
>>> A # Tensor of rank 2
array([[1, 2, 3],
[4, 5, 6]])

>>> B = np.array([[1, 2, 1], [1, 0, 2]])


>>> B # Tensor of rank 2

array([[1, 2, 1],
[1, 0, 2]])
>>> np.tensordot(A, B, axes = 0)
array([[[[ 1, 2, 1],
[ 1, 0, 2]],

[[ 2, 4, 2], [ 2, 0, 4]],
[[ 3, 6, 3], [ 3, 0, 6]]],
[[[ 4, 8, 4], [ 4, 0, 8]],
[[ 5, 10, 5], [ 5, 0, 10]],
[[ 6, 12, 6],
[ 6, 0, 12]]]]) # Result is Tensor of rank 3

Note: Consult the online help for np.tensordot()to look at the other kinds of
tensor products set byaxes keyword:axes = 1 for tensor dot product andaxes
= 2 for tensor double contraction. The axes refer to the axis on which to take
sum of products (the usual axis reference ofndarray in NumPy).

>>> C = B.reshape(3, 2)
>>> D = np.tensordot(A, C, axes =

1) >>> D
array([[ 3, 10],

[ 9, 25]])
[Equivalent Matrix product]

Online help reads: ‘When there is more than one axis to sum over - and they
are not the last (first) axes of A (B) - the argument `axes` should consist of
two sequences of the same length, with the first axis to sum over given first
in both sequences, the second axis second, and so forth. The shape of the
result consists of the non-contracted axes of the first tensor, followed by the
non-contracted axes of the second.’

# Multiple axes (Contraction)

>>> D = np.tensordot(A, B, axes = ([0, 1], [0, 1]))


>>> D
array(24)

Applications:

Tensors and Tensor algebra are important mathematical tools widely used in
Mathematics, Physics and Engineering. There are techniques developed in
Deep Learning (in the area of Machine Learning) based on this. Each layer
(input, output, or hidden layer of Neural network) can be thought of as a
NumPy array. The multiple layers are connected through the weighted sum
where all the elements between two arrays (fully connected network) are
connected like a tensor product way. Google’s flagship libraryTensorFlow is
based on this idea.

8.9 NumPy Functions

The mathematical functions such as sin, cos, exp, sqrt etc. in NumPy are
called “universal functions” (ufunc). Within NumPy, these functions operate
elementwise on an array, producing an array as output. In other words,
functions in NumPy are all vectorized. Normally the user defined functions
(or the functions that we imported frommath, cmath modules) operate on
scalar (default argument is a single element). But in NumPy, the argument of
a function can be iterable likearray, list, tuple.

[About universal functions from NumPy Document: ‘A universal function


(orufunc for short) is a function that operates on ndarrays in an element-by-
element fashion. That is, a ufunc is a “vectorized” wrapper for a function that
takes a fixed number of specific inputs and produces a fixed number of
specific outputs. In NumPy, universal functions are instances of
thenumpy.ufunc class.’]

For example,
>>> x = array([1., 1.5, 2., 2.5])

>>> y = np.exp(x)
>>> y
array([ 2.71828183, 4.48168907, 7.3890561 , 12.18249396])
# Vectorized math operations

>>> a = np.array([1, 2, 3]) >>> a**2


array([1, 4, 9], dtype=int32)

Note: All the functions in NumPy are vectorized whereas the functions with
the same names in math module (in core Python) are not. To see the
difference,

>>> import math


>>> x = array([1., 1.5, 2., 2.5]) >>> math.exp(x) # Not vectorized
Traceback (most recent call last): File "<pyshell#421>", line 1, in <module>
math.exp(x)
TypeError: only size-1 arrays can be converted to Python scalars

# List in core Python >>> a = [1, 2, 3]

>>> a**2 # No vectorized op possible Traceback (most recent call last): File "
<pyshell#436>", line 1, in <module>

a**2
TypeError: unsupported operand type(s) for ** or pow(): 'list' and 'int'

Application
# Python Script to plot NumPy function

import numpy as np
# NumPy array created x = np.arange(-2, 2, 0.1)
# Another array with NumPy function y = x*np.exp(-x**2)

# For plotting
import matplotlib.pyplot as plt plt.plot(x, y)
plt.show()

Fig. 4 Plotting with NumPy vectorized functions.


As it is said, NumPy functions are vectorized. The argument of any NumPy
function can be any scalar or iterable (list, tupleorNumPy array). The
dimension of any argument can be anything, of any shape. NumPy functions
will act on each element.

>>> x = [0, 0.5, 1, 1.5] # List >>> np.sin(x)


array([0. , 0.47942554,

0.84147098, 0.99749499]) >>> x = (0, 0.5, 1, 1.5) # Tuple >>> np.sin(x)

array([0. , 0.47942554,
0.84147098, 0.99749499])

>>> x = [2.58, -3.934, 6.91, 12.38] >>> np.floor(x)


array([ 2., -4., 6., 12.]) [Floor of every number]

>>> np.ceil(x)
array([ 3., -3., 7., 13.]) [Ceiling of every number]

>>> X
array([[ 0. , 0.5, 1. ],
[ 2. , 3. , 4. ],
[10. , -3. , 5. ]])
>>> np.sin(X)
array([[ 0. , 0.47942554,
0.84147098],
[ 0.90929743, 0.14112001,
-0.7568025 ],
[-0.54402111, -0.14112001,
-0.95892427]]) >>> np.ceil(np.sin(X))

array([[ 0., 1., 1.], [ 1., 1., -0.], [-0., -0., -0.]])

8.10 Vectorize a Function

A user defined function (or a mathematical function in math module in core


Python) is not vectorized. But we can vectorize it with the NumPy method
vectorize().
>>> x = [1, 2, 3, 4] >>> def f(x):
return x**2

# Usual function, not vectorized >>> f(x)


Traceback (most recent call last):

File "<pyshell#470>", line 1, in

<module>
f(x)
File "<pyshell#469>", line 2, in f return x**
TypeError: unsupported operand type(s) for ** or pow(): 'list' and 'int'

# Vectorizing the function >>> f = np.vectorize(f) >>> type(f)


<class 'numpy.vectorize'> >>> f(x)
array([ 1, 4, 9, 16])

# Not vectorized
>>> math.sqrt(x)
Traceback (most recent call last):

File "<pyshell#18>", line 1, in

<module>
math.sqrt(x)
TypeError: must be real number, not
list

# Vectorized now
>>> s = np.vectorize(math.sqrt) >>> s(x)
array([1., 1.41421356, 1.73205081,

2.])
8.11 Polynomial in NumPy

One dimensional Polynomial Class, poly1d()is to create a 1D polynomial. To


construct a polynomial,��2+ 2�� + 3, through the function, we must give
the list of coefficients [1, 2, 3] as argument.
>>> from numpy import poly1d >>> p = np.poly1d([1, 2, 3]) >>> type(p)
<class 'numpy.poly1d'>

# Display coeffs on interpreter >>> p


poly1d([1, 2, 3])

# Display of Polynomial >>> print(p)


1��2+ 2�� + 3
>>> p(2)
11
>>> p(-1)
2

Note: In the above,p is an object created from the poly1d class. This is a
function so that we can write p(x). We can know the attributes of the object
as follows.

# Coefficients
>>> p.c array([1, 2, 3])

# Order of the polynomial


>>> p.order 2

8.11.1 Arithmetic Operations >>> p1 = poly1d([1, 2, 3])

>>> p2 = poly1d([1, -2]) >>> p1 + p2


poly1d([1, 3, 1])
>>> p1 - p2
poly1d([1, 1, 5])
>>> p1 * p2
poly1d([ 1, 0, -1, -6]) >>> p1/p2

(poly1d([1., 4.]), poly1d([11.])) [Tuple of quotient, remainder]

8.11.2 Functions of polynomial # ��(��)= �� + 2


>>> f = np.poly1d([1, 2])

>>> f * f # ��2+ 4�� + 4 poly1d([1, 4, 4])


>>> f**2 + np.sin(f)
poly1d([1., 4.841470, 4.909297])

8.11.3 Roots of Polynomial Equation >>> p = np.poly1d([1, -5, 6]) >>>


p.r
array([3., 2.]) # Real roots: 3, 2

>>> p = np.poly1d([1, 2, 3]) >>> p.r


array([-1.+1.41421356j, -1.

1.41421356j]) [Complex roots]


# Python Scrip: Plot the polynomial

from numpy import poly1d


import matplotlib.pyplot as plt p = poly1d([1, -5, 6])
x = np.linspace(1, 4, 100)
plt.plot(x, p(x), lw = 3)
plt.axhline(linestyle = '--',) plt.show()

Fig. 5
Polynomial:
��2− 5�� + 6
[Two roots can
be identified
from the
crossing of
axis.]
x

8.11.4 Derivative

>>> p = np.poly1d([1, 2, 3]) >>> p.deriv(1) # First derivative poly1d([2, 2])


>>> p.deriv(2) # Second derivative

poly1d([2])
8.11.5 Indefinite Integral

>>> p.integ(1) # Integrated once poly1d([0.33333333, 1. , 3. , 0. ]) >>>


p.integ(2) # Integrated twice poly1d([0.08333333, 0.33333333,

1.5, 0., 0. ])
8.12 Scientific Modules in NumPy

There are various modules in NumPy to do scientific computations. We shall


emphasize on the following two modules:

• polynomial
• linalg (For Linear Algebra)

The polyfit() function in polynomial module will be used for curve fitting.
This will be discussed in detailin the ‘Numerical Methods’ chapter.

8.12.1 NumPy Polynomial Module

“ Polynomials in NumPy can be created, manipulated, and even fitted using


the convenience classes of thenumpy.polynomial package, introduced in
NumPy 1.4.

Prior to NumPy 1.4,numpy.poly1d was the class of choice and it is still


available in order to maintain backward compatibility. However, the newer
Polynomial package is more complete thannumpy.poly1d and its convenience
classes are better behaved in the NumPy environment.
Thereforenumpy.polynomial is
recommended for new coding.”

[Fromnumpy.org/doc]

>>> import numpy.polynomial as poly >>> p = poly.Polynomial([1, 2, 3])


>>> p
Polynomial([1., 2., 3.], domain =

[-1, 1], window = [-1, 1]) >>> print(p)

poly([1. 2. 3.]) >>> p(2)


17.0

Note: The various routines in the Polynomial package all deal with series
whose coefficients go from degree zero upward, which is the reverse order of
the Poly1d convention. There are polynomial power series, special function
polynomials and all that you can explore.

The Polynomial class in numpy.polynomial.polynomial also deals with


polynomials. The polynomial, in this case, will be of the form:1 + 2�� +
3��2 (Powers are in reverse order).

8.12.2 Linear Algebra with NumPy >>> import numpy as np


>>> v = np.array([1, 0, -1, 2])

# Norm of a vector >>> np.linalg.norm(v) 2.449489742783178 #


Determinant

>>> A = np.array([[1, 0, 3], [2, 4, 1], [0, 6, 3]])


>>> A
array([[1, 0, 3],
[2, 4, 1],
[0, 6, 3]])
>>> np.linalg.det(A) # Determinant
42.00000000000001

# Rank of Matrix
>>> np.linalg.matrix_rank(A) 3
# Inverse of Matrix

>>> B = np.linalg.inv(A)
>>> B
array([[ 0.14285714, 0.42857143,

-0.28571429], [-0.14285714, 0.07142857, 0.11904762], [ 0.28571429,


-0.14285714,

0.0952381 ]]) # To check


>>> np.dot(A, B)
array([[1., 0., 0.],

[0., 1., 0.],


[0., 0., 1.]])
[Product with inverse gives Identity.]
Application:

To solve the following set of linear equations: ��1+ 2��2− ��3= 1


2��1+ ��2+ 4��3= 2 3��1+ 3��2+ 4��3= 1
The above set of equations can be written in the matrix form: AX = B, where

1 2 −1 ��1 1
A = (2 1 4 ), X= (��2), and B= (2)
3 3 4��3 1

Thus, we write, �� = ��−����


# Python Script: Solve
import numpy as np
A = np.array([[1, 2, -1], [2, 1,

4], [3, 3, 4]]) B = np.array([1, 2, 1])


Ainv = np.linalg.inv(A)
X = np.dot(Ainv, B)
print(X)

Output:
array([ 7., -4., -2.])
# Direct Solve
>>> np.linalg.solve(A, B) array([ 7., -4., -2.])
# Eigenvalues, Eigenvectors >>> A
array([[1, 2],
[3, 4]])
>>> np.linalg.eig(A)

(array([-0.37228132, 5.37228132]), array([[-0.82456484, -0.41597356], [


0.56576746, -0.90937671]]))

Note: The eig() function returns a tuple of arrays. The first array is a
collection of two eigenvalues and the second array is a collection of two
eigenvectors. The two columns of the second array are two eigenvectors.

# Eigenvalues and Eigenvectors unpacked

>>> u, v = np.linalg.eig(A) >>> u # Eigenvalues array([-0.37228132,


5.37228132])

>>> v # Eigenvectors array([[-0.82456484, -0.41597356], [ 0.56576746,


- 0.90937671]])
# Eigenvalues >>> u1, u2 = u

>>> u1 # Eigenvalue 1
-0.3722813232690143
>>> u2 # Eigenvalue 2
5.372281323269014

# Form a diagonal matrix with eigenvalues

>>> np.diag(u) # Diagonal Matrix array([[-0.37228132, 0. ], [ 0. ,


5.37228132]])

# Eigenvectors

>>> v[:, 0] # Eigenvector 1


array([-0.82456484, 0.56576746]) >>> v[:, 1] # Eigenvector 2
array([-0.41597356, -0.90937671])

# For eigenvalues only


>>> np.linalg.eigvals(A)
array([-0.37228132, 5.37228132])
For Complex Matrices

The function eigh() computes the eigenvalues and eigenvectors of a complex


Hermitian or a real symmetric matrix. [Hermitian is a complex square matrix
which is equal to its own conjugate transpose.]
# Hermitian matrix

>>> M = np.array([[1, 1+1j, 2j], [1-1j, 4, 2-3j], [-2j, 2+3j, 7]]) >>> M
array([[ 1.+0.j, 1.+1.j, 0.+2.j],

[ 1.-1.j, 4.+0.j, 2.-3.j], [-0.-2.j, 2.+3.j,

7.+0.j]]) # To test if Hermitian


>>> M.conj().T == M
array([[ True, True, True],

[ True, True, True], [ True, True, True]]) # Eigenvalues and Eigenvectors of


Hermitian Matrix

>>> np.linalg.eigh(M)
(array([-0.66765836, 2.91919537, 9.74846299]), array([[
0.78102045+0.j, 0.59215952+0.j, 0.19837883+0.j],
[-0.46839474+0.12569175j,
0.6949742 -0.0055707j,
0.23041631+0.47822181j],
[0.1713515 +0.3541944j,
0.34470175-0.21796245j,
0.35431906+0.74385218j]]))

Note: The function eigh() returns eigenvalues as real and the eigenvectors as
complex as it should be the case for a Hermitian matrix.

# Hermitian Conjugate

>>> M = np.matrix([[1, 1+1j, 2j], [1-1j, 4, 2-3j], [-2j, 2+3j, 7]])

>>> M.H # Hermitian Conjugate

matrix([
[ 1.-0.j, 1.+1.j, -0.+2.j], [ 1.-1.j, 4.-0.j, 2.- 3.j], [ 0.-2.j, 2.+3.j, 7.- 0.j]])

Note: To find Hermitian conjugate, the array must be in matrix type.


8.13 Grid by NumPy Arrays

NumPy has methods to create grid of numbers from coordinates. In the


following, we consider meshgrid() which creates coordinate matrices from
coordinate vectors.

>>> x = np.array([0, 1, 2, 3]) >>> y = np.array([0, 1, 2]) >>> X, Y =


np.meshgrid(x, y) >>> X
array([[0, 1, 2, 3],

[0, 1, 2, 3],
[0, 1, 2, 3]])
>>> Y
array([[0, 0, 0, 0],
[1, 1, 1, 1],
[2, 2, 2, 2]])

Note: The meshgrid(x, y) creates a grid where the coordinates of the grid
points are (0, 0), (0, 1)….(1, 0), (1, 1)….etc. Now we can define a function at
every grid point.

Application: To create a contour plot


# Python Script

import numpy as np
import matplotlib.pyplot as plt x = np.linspace(-1, 1, 100) y = np.linspace(-1,
1, 100) X, Y = np.meshgrid(x, y)
Z = np.exp(-X**2 + Y**2)
plt.contour(X, Y, Z)
plt.show()
Fig. 6 Contour plot throughmeshgrid.
Dense grids can also be created through some other object in NumPy called
mgrid().

>>> x, y = np.mgrid[0:5, 0:4] >>> x


array([[0, 0, 0, 0],

[1, 1, 1, 1],
[2, 2, 2, 2],
[3, 3, 3, 3],
[4, 4, 4, 4]])

>>> y
array([[0, 1, 2, 3],
[0, 1, 2, 3], [0, 1, 2, 3], [0, 1, 2, 3], [0, 1, 2, 3]])
# For Contour plot

x, y = np. mgrid[0:1:0.1, 0:1:0.2] z = x**2 + y**2


plt.contour(x, y, z)

8.14 Load and Save Datafile

Plain text or datafiles are some files where data or text are written with space
separated or comma separated way. The files may show .dat and .txt
extensions, where .txt is often used for files containing plain text or data.
These files can be opened and viewed by simple text editor. We may put.dat
as extension to indicate a data file. But file extensions are not mandatory. In
Windows, files can be saved as Text or DAT file.

NumPy methods can handle files with pure numbers. The functionsavetxt() is
to save data in a file andloadtxt() is to read or load data from a file.

Data File: test.dat


0.74 -0.68 -0.77

0.25 -0.95 0.07


-0.09 0.06 -0.28
-0.68 0.99 0.49 0.78 0.63 -0.02 0.65 0.09 0.19 0.68 0.03 0.09
-0.17 0.28 0.84

# Save data into a file


>>> np.savetxt('test.dat', data)
# Load data from a file

>>> x = np.loadtxt('test.dat') >>> x


array([[ 0.74, -0.68, -0.77],

[ 0.25, -0.95, 0.07], [-0.09, 0.06, -0.28], [-0.68, 0.99, 0.49], [ 0.78, 0.63,
-0.02], [ 0.65, 0.09, 0.19], [ 0.68, 0.03, 0.09], [-0.17, 0.28, 0.84]])

# Create data file at a specific destination (include path)


np.loadtxt('C:/Users/Abhijit/ Desktop/Demo/test.dat')
# File read with delimiter
np.loadtxt(‘test.dat’,
delimiter = ‘,’)

When data are written with comma (,) separated way in a file, we need to
usedelimiter = ‘,’ as keyword argument.

Another method of generating NumPy array from a data


file:genfromtxt(‘test.dat’).
8.15 Random Numbers in NumPy

There are various random number generators in the module ‘random’ in


NumPy. Uniform, normal or other random variates, random number
generators for integer random numbers and various other types that are useful
for our scientific calculations, can be seen from the list of names. As usual,
we can seek online help for each of them by typing help(np.random.uniform)
and so on.

The important thing is, we can create random arrays of any size and shape
directly by keyword arguments.

>>> dir(np.random)
['BitGenerator', 'Generator', 'MT19937', 'PCG64', 'Philox', 'RandomState',
'SFC64', 'SeedSequence', '__RandomState_ctor', '__all__', '__builtins__',
'__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__',
'__path__', '__spec__', '_bit_generator', '_bounded_integers', '_common',
'_generator', '_mt19937', '_pcg64', '_philox', '_pickle', '_sfc64',
'absolute_import', 'beta', 'binomial', 'bytes', 'chisquare', 'choice', 'default_rng',
'dirichlet', 'division', 'exponential', 'f', 'gamma', 'geometric', 'get_state',
'gumbel', 'hypergeometric', 'laplace', 'logistic', 'lognormal', 'logseries',
'mtrand', 'multinomial', 'multivariate_normal',
'negative_binomial',
'noncentral_chisquare',
'noncentral_f', 'normal', 'pareto', 'permutation', 'poisson', 'power',
'print_function', 'rand', 'randint', 'randn', 'random', 'random_integers',
'random_sample', 'ranf', 'rayleigh', 'sample', 'seed', 'set_state', 'shuffle',
'standard_cauchy', 'standard_exponential',
'standard_gamma', 'standard_normal', 'standard_t', 'test', 'triangular', 'uniform',
'vonmises', 'wald', 'weibull', 'zipf']
# Random number between 0 and 1 >>> import numpy as np

# Random number in [0.0, 1.0) >>> np.random.random()


0.796924428952941

Note: The method random()creates numbers, randomly between the half-


open interval:[0.0, 1.0), which means the number�� ≥ 0 and �� < 1.

Arrays of random numbers

# 1D array of 10 numbers
>>> np.random.random(size = 10) array([0.80202971, 0.2630612,
0.33344721, 0.59599463,
0.73809468,0.73806876, 0.60964991, 0.16738648, 0.96946575, 0.6322321 ])

# Creating 3 × 4 array
>>> np.random.random((3, 4))
array([[0.72084356, 0.88093821, 0.83525373, 0.46332243],

[0.82524178, 0.50718295, 0.89747926, 0.07966796],


[0.58582415, 0.16378067, 0.08843958, 0.25770781]])

# Uniform Distribution
The function uniform(low, high)draws

samples from a uniform distribution which are uniformly distributed over the
half-open interval [low, high) (includes low, but excludes high).

# Between 1 and 2
>>> np.random.uniform(1, 2) 1.9496816388917961
>>> np.random.uniform(-1, 1, 1000) [1000 random numbers created.]

Application: Monte Carlo estimate of �� # Python Script

import numpy as np
x = np.random.uniform(-1, 1, 1000) y = np.random.uniform(-1, 1, 1000) z
= x**2 + y**2 < 1
pi = 4*sum(z)/1000
print(‘pi = ‘, pi)
Output: pi = 3.136
# Normal Distribution
# With mean = 0, variance = 1 >>> np.random.randn(1000)
# With mu = 0.5, sigma = 2
>>> np.random.normal(0.5, 2, 1000)

The NumPy function randn()returns a sample (or samples) from a standard


Normal distribution with mean = 0 and standard deviation = 1. The
functionnormal(mu, sigma) draws random numbers from the Normal
(Gaussian) distribution with supplied mean (mu) and standard deviation
(sigma). We created arrays of 100000 random numbers by both methods.

Fig. 7 Histograms drawn with uniform and Normal random numbers.

To understand that the random numbers are drawn from some distributions,
we can collect the randomly generated numbers by some random number
generator and plot them in the form of histograms (Frequency distribution). A
histogram shows the character of the distribution. For the above, we
generated two sets of 100000 random numbers by uniform and Normal
random number generators and plotted side by side.

# Random integers
# 3 × 4 array of integers
>>> np.random.randint(10, 50, (3,

4)) array([[33, 11, 18, 18],


[40, 22, 31, 24],
[34, 30, 25, 35]])

Note: Random integers between 10 and 50 where the upper limit is excluded
(half-open interval).
# Random Choice
>>> x = [1, 2, 3, 4, 'a', 'b', 'c',

'd'] # Choose a sample of size 3


>>> np.random.choice(x, 3)
array(['d', '3', 'b'],

dtype='<U11') >>> x = np.arange(10)


# Choice from array x
>>> np.random.choice(x, size = (4,

5)) array([[5, 3, 3, 3, 1],


[2, 4, 1, 3, 2],
[4, 8, 8, 8, 1],
[0, 9, 4, 6, 2]])

>>> np.random.choice([0, 1], p = [0.3, 0.7], size = (4, 5))


array([[0, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[0, 0, 1, 1, 1],
[1, 0, 1, 0, 1]])

Note: The default argument in choice() has to be one dimensional array.


Here, p = [0.3, 0.7] is a list of probabilities corresponding to the numbers 0
and 1 that are to be chosen.

Application:

Consider the Monte Carlo simulation of 2D Ising model of spins (2-state) of


system size100 × 100. In this system, 80% spins are to be of down state and
the rest 20% spins are to be up. The code for this is just the following one
liner.

np.random.choice([0, 1], p = [0.8, 0.2], size = (100, 100))


# Random Shuffling
Modify a sequence in-place by shuffling its contents.

>>> x = array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])


>>> np.random.shuffle(x)
[The array is permuted.]

>>> x
array([0, 9, 4, 8, 3, 6, 7, 5, 2, 1])
# Random Permutation

# Returns a new array


>>> np.random.permutation(x) array([5, 6, 7, 0, 9, 8, 3, 1, 4,

2])
>>> x
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) [Original array unchanged.]

# Start with a seed for the same sequence # Give any seed number

>>> np.random.seed(283)
>>> np.random.random()
0.5974151257398422
>>> np.random.random()
0.32662323744657806

# Give same seed, get same sequence >>> np.random.seed(283)


>>> np.random.random()
0.5974151257398422
>>> np.random.random()
0.32662323744657806

Note: In case we start with any fixed integer as seed, we will get the same
sequence of (pseudo) random numbers.
Exercises
Write Python scripts for the following.

1. Create a one-dimensional NumPy array of integers between 0 to 24.


Reshape the array into (5, 5) array.

2. Create a full 10 × 10 matrix with all elements equal to 5.


3. Create the set of numbers between 0 to 1 with successive intervals of
0.125.
4. Consider the following NumPy arrays:x = [[1, 2, 3], [4, 5, 6]] and y = [[6,
5, 4], [3, 2, 1]]. Concatenate the two arrays and check what you find.
5. Consider the following NumPy array:A = [1, 2, 3, 4, 5, 6, 7, 8, 9]. Split this
into three one dimensional NumPy arrays of equal length.
6. Create a one-dimensional NumPy array of 100 numbers between 0 and 1.
Find out the maximum and replace that with 99. [Hint: Find out the max and
find out where it appears. Use appropriate NumPy methods.]
7. Create 100000 random integers, either 0 or 1. Think of 0 as Tail and 1 as
Head of a coin. Find out how many ‘Head’ comes out in your collection of
numbers (Total trial). Compute the fraction of Head and that of Tail.
8. Create a 5 × 5 Matrix (a 2D array) of random integers between 1 and 10.
Find out where the 0-values occur.
9. Using Numpy, create a 3 × 3 × 3 array of random numbers between 0 and
1.
10. Generate a 4 × 5 array of of random integers between 1 and 10. Generate
another such array with random numbers between 0 and 1. Sum the two
matrices.
11. Create a Numpy array of 50 numbers between 0 and 4�� with equal
interval. Create another array where each member in it, is the cosine of the
corresponding member of the previous array.
12. Using Numpy package create a random vector of size 20 and replace the
maximum value by 0. [Hint: Use the np.max() and np.where() methods.]
13. Define a function:��(��)= ��2+ 2��. Now consider �� from
an array consisting of numbers between 1 and 5 with equal interval = 0.1.
Now create an array for the function ��(��) and take the sum of the
elements.
14. Create two lists X and Y, each having 100000 integers (Either by some
random number generators or through range function). Now, try to add the
corresponding elements in them by List comprehension method to form a
new list Z. Import ‘time’ module to check how much time it took. Now,
create two similar NumPy arrays X and Y and add the two arrays. Also,
check the time taken this time. Compare time for the two processes.
15. Create a 1D NumPy array of 10000 random integers between 1 and 100.
Reshape this into a 100×100 2D array. Make another array from it which will
consist of the last 10 rows and 10 columns. Make another array from the last
array which will consist of all even numbers.
16. Consider the following 2D array: �� = 9 7 8 4

(9 8 6 4). Find the eigenvalues4 3 4 8

6185
and eigenvectors of this matrix. Form the diagonal matrix ���� out of the
eigenvalues. Check if the eigenvectors are orthogonal or not.

17. Make a 10 × 10 with random integers between 1 to 10. Find the


determinant, inverse of the matrix. Find the eigenvalues and eigenvectors.
Make the diagonal matrix out of the eigenvalues.

18. Find the eigenvalues and the eigenvectors of 01 0√2


1 01 usingNumPy.
√2 √2
0
1
( √2 0)

i) Find the norm of the eigenvectors thus obtained, ii) Show that the
eigenvectors are orthogonal.

19. A single data point is measured repeatedly, and the following values were
obtained:5, 5.5, 4.9, 4.85, 5.25, 5.05, 3.25. Find out the mean, variance,
standard deviation, sem (standard error to mean) for the set of values. You
can use appropriate functions fromNumPy package.

CHAPTER
9
Matplotlib: Plotting Library
“It is a capital mistake to theorize before one has data.”- Arthur Conan
Doyle

Matplotlib is a comprehensive library for creating static, animated, and


interactive visualizations in Python. Like any other (open source) Python
packages, it can be installed in the Python directory and be imported to the
namespace.

Anaconda , Python(x,y) or some other scientific Python distributions come


with packages like NumPy, SciPy and Matplotlib. In most of the latest
versions of Linux Operating system, Python is preinstalled with the said
packages. If not found already installed, Matplotlib can be installed over core
Python in Windows by usual command:

pip install matplotlib [See Appendix A]. On Linux, it is done through the
command: sudo. For detailed procedure, documentation, and tutorials, one
can look up the official Matplotlib webpage: matplotlib.org.

9.1 Plotting in 2D
9.1.1 X-Y Plots

Among various modules and functions in matplotlib, we here deal with the
pyplot module which contains functions that allow us to generate various
kinds of 2D plots [(x, y)-plot].

# Import, plot and show [On Interpreter]


>>> import matplotlib.pyplot as plt
# To create the plot >>> plt.plot()
# For display >>> plt.show()
# See Directory, ask for help # See the directory
>>> dir(plt)
# Help over plot() function >>> help(plt.plot)
# Simple plot
x = [1, 2, 3, 4, 5] y = [1, 4, 9, 16, 25] plot(x, y)
plt.show()

# Default continuous line, blue colour

Fig. 1 Simple x-y plot. [If nothing is mentioned, the plot is by default with
continuous line and green colour (not reproduced here).]

The x- and y- data may be given aslist, tuple, array etc. We can write,
# Data as tuples
plt.plot((1, 2, 3, 4, 5), (1, 4, 9,
16, 25)) # Plot y using x as 0, 1, 2, .. (default) plt.plot(y)

# Plot a range of numbers plt.plot(range(10))


# Scatter plot with other function plt.scatter(x, y)
We can create any matplotlib plot by creating a figure and axes.

# Create new object fig fig = plt.figure()


ax = plt.axes()

The ‘ figure’ can be thought of as a single container and the ‘axes’ creates the
frame of axes with ticks etc. After this, we canrefer ‘fig’ and ‘ax’ every time
we add something. Most plt functions translate directly to ax methods such as
plt.plot ⟶ ax.plot(),
plt.xlabel() ⟶ ax.xlabel(), plt.xlim() ⟶ ax.set_xlim(), plt.title() ⟶
ax.set_title().

Style with keyword arguments

We can format the plot with various keywords (insideplot function) for line
style, line width, maker style, maker size etc.

# Plot with line, marker, color

plt.plot(x, y, linestyle = ‘-’, marker = ‘o’, color = ‘black’) plt.show()


Fig. 2 Modification over Fig.1. Plots are with bullet point markers and
continuous lines. Color is black. The keyword arguments can be written with
abbreviations. Also, arguments can be put without keyword names when
placed at appropriate positions. For example,

# line and circle, color black plt.plot(x, y, ‘-o’, c = ‘k’)


# Plot with blue, circle markers plt.plot(x, y, ‘bo’)
# Plot with red, plus markers plt.plot(x, y, ‘r+’)
[‘-o’ = line and circle, c = color, ‘k’ = black, ‘bo’ = blue circle, ‘r+’ = red
plus]
We can do much more. The plotting styles are almost self-explanatory as
follows.
# Plot with various styles

plt.plot(x, y, c = ‘r’, l s = ‘-’, lw = 3, m = ‘o’, ms = 16)


plt.show()
Fig. 3 Bullet points bigger, line width increased [Color is not shown red in
print.]

The abbreviations in the plot:


‘r’ = red, ls = linestyle, lw = linewidth, m = marker, ‘o’ = bullet point, ms =
markersize

Matplotlib functions
# Add title above the frame
title()
# Add title further above
suptitle() # Super title
# Add text at a given pos in figure text()
# Add text at a given pos
figtext() # in relative coordinates
# Add annotation
annotate() # with optional arrow

xlabel() Label to x-axis ylabel() Label to y-axis xlim() x-axis limits to show
ylim() y-axis limits to show xticks() Tick marks along x-axis yticks() Tick
marks along y-axis grid () Plot with grid

plt.title('Given Title', size = ..) plt.text(x-pos, y-pos, 'Text', size = ..)

plt.xlim(min, max)
plt.ylim(min, max) plt.grid()

Marker Colors
b(blue), r(red), g(green), y(yellow), k(black), m(magenta),c(cyan), w(white)

We can create colors by mixing three primary colors RGB (Red, Green,
Blue). To customize, we need to give (r, g, b) as tuple. Also, we can setmec
= marker edge color.

To use:
• For colors: r, g, b = (0.1, 0.3,

0.7) , each value in tuple is in range [0, 1].


• For monochrome: Black (0), …Grey
(0.5),…White (1), in this order.
With a single number (between 0 to 1), we
can create various depths of monochrome:
fromblack, to less black to grey…to white. Marker Styles

Point ( .), Plus (+), Thick plus (P), Cross (x), Thick cross (X), Star (*), Solid
circle (o), Solid triangle up (^), Solid triangle down (v), Solid triangle left
(<), Solid triangle right (>), Solid square (s), Solid pentagon (p), Solid
hexagon (h), Solid hexagon of different orientation (H), Solid Octagon (8),
Solid diamond (D), Solid diamond thin (d), Vertical line (|), Horizontal line
(_), Tripod down (1), Tripod up (2), Tripod left (3), Tripod right (4).

Line Styles
-‘ (Continuous line), ‘-‘ (broken line), ‘-.’ ‘
(LineDot), ‘:’ (Dashed line)

# Application of colors
# color

plt.plot(x, y, '8', c = (0.9, 0.1, 0.8), ms = 20)


# monochrome
plt.plot(x, y, 'H', c = '0.1', ms = 20)

Marker styles: 8(Octagon), ‘H’ (Hexagon) c = color,ms = marker size

Name and size of a figure


A ‘figure’ in matplotlib means the whole window in the user interface.

plt.figure(1, figsize = (8, 6), dpi = 100)


plt.figure(‘Demo figure’, figsize = (10, 6))

Note: The argument dpi (dots per inch) refers to the physical dot density of
an image when it is reproduced as a real physical entity, for example when
printed onto paper.

num number of figure (default = 1) figsize figure size in inches (width,


height)
dpi resolution in dots per inch facecolor color of the drawing backgroud
edgecolor
frameon

color of edge around the drawing background


drawing figure frame or not (default = True)

The keyword arguments inplt.figure() that determine how a figure looks like.
# Example: Plot with styles plt.figure(figsize = (12, 8))

plt.plot(x, y, color = ‘black’, linestyle = ‘-’, lw = 3, marker = ‘o’, ms = 16,


label =
‘Temperature’)

# x, y-axis labels
plt.xlabel(‘X-axis’, size = 20) plt.ylabel(‘Y-axis’, size = 20)

# title, super title


plt.title(‘This is my Demo Graph’, size = 20) plt.suptitle(‘Figure 4’, size =
22)
# Write legend (inside graph)
plt.legend(loc = 2, fontsize = 18)
# Location of label inside
plt.xticks([1, 3, 5], size = 18)

# Ticks at points, size


plt.yticks([1, 9, 16], size = 18) plt.tick_params(axis = ‘x’,

labelcolor = ‘g’, labelsize = 16)


# Show the graph
plt.show()
Fig. 4 It shows the legend (text inside), title, supertitle, axis labeling and
other styles. [Colors will not show here in this book.]

Legend
The legend() function places a legend (a box

inside the graph with label) on the graph (by default at the ‘best’ position).
We can position the legend with loc =‘upper left’, ‘lower right’, ‘upper right’,
‘lower left’ or indicate with numbers.

=============== ============= Location String Location Code


=============== ============= 'best' 0 'upper right' 1 'upper left' 2
'lower left' 3 'lower right' 4 'right' 5 'center left' 6 'center right' 7 'lower center'
8 'upper center' 9 'center' 10 =============== =============

# Plotting with NumPy arrays and functions


import numpy as np
x = np.arange(0.5, 10, 0.5) y = np.exp(-x)

# With line and bullets plt.plot(x, y, '-o')


# Plotted with grid plt.grid()
plt.show()

Fig. 5 NumPy function plotted with grid.

# Vectorize a function and plot # User defined function


>>> f = lambda x: x**2 – 3

# Vectorize it
>>> f = np.vectorize(f)
>>> x = range(10)

# plot
>>> plt.plot(x, f(x))

Horizontal line
# Default h-line at y = 0 plt.axhline()

# h-line at y = 2
plt.axhline(y = 2, lw = 2, c = ‘r’)
# Introduce range set
plt.axhline(y = 1, xmin = 0, xmax = 4)
Vertical line
plt.axvhline()

Note: To arguments,color = ‘red’(c = ‘r’), line width = 2 (lw = 2). The


plt.axhline() orplt.axvline() with no arguments, draw lines with default width,
default color, default range.

# Example: Plot horizontal line x = np.linspace(-2*np.pi, 2*np.pi, 100)


plt.plot(x, np.sin(x), lw = 2)
# Horizontal axis drawn
plt.axhline(linestyle = '--')
# X-axis limits shown in plot plt.xlim(-6, 6)
plt.show()
Fig. 6 Plot with horizontal line, with x-axis limits.
# To fill the space in between x = np.linspace(0, 4*np.pi, 100) y = np.sin(x)

plt.plot(x, y, color = 'black', lw = 2)


plt.fill_between(x, 0, y, color = 'gray')
plt.show()
Fig. 7 The functionfill_between (x, 0, y) fills the space between two
horizontal lines y = 0 and y.
The apace filling can also be done with conditions through keyword
argument. For example,
>>> plt.fill_between(x, y, 0, where
Log-scale

semilogx() semilogy() loglog() semilogx(x, y) Default base e semilogx(x, y,


basex = 2)
loglog(x, y, basey = 10)
= (y > 0) & (x > 2))
x-axis in log y-axis in log x & y both are in log # Plot in log-log scale x =
np.linspace(1, 100, 10) y = x**3

# For log-log plot


plt.loglog(x, y, '-^', ms = 12) plt.xlabel('Time', size = '18')
plt.ylabel('Temperature', size =

18)

Fig. 8 Plot in log-log scale


# Plot in Semi-Log scale
import matplotlib.pyplot as plt import numpy as np
x = np.arange(0, 10, 0.01)
plt.semilogx(x, np.sin(2*np.pi*x),

color = ‘0.1’ , lw = 2) plt.title('Semi-log x', size = 20) plot.show()

# Semi-Log y

plt.semilogy(x, x**2*np.exp(-x), color = ‘0.1’, lw = 2)


plt.title('Semi-log y', size = 20)
Fig. 9 Semi-log x Fig.
10 Semi-log y
# Electronic Signals from SciPy

from scipy.signal import square, sawtooth


x = np.linspace(-10 , 10 , 1000)
plt.plot(x, square(x))
Fig. 11 Square wave signal from SciPy
# Lissajous Figure

Lissajous figure is a pattern produced by the intersection of two sinusoidal


signals along the axes that are at right angles to each other. Basically, this is a
graph produced by a system of parametric equations.

t = np.linspace(0,1,100)
wt = 2*np.pi*t
x = np.sin(4*wt)
y = np.cos(wt)

plt.axes().set_aspect('equal') plt.xlim(-1.0, 1.0)


plt.ylim(-1.0, 1.0)
plt.plot(x, y, lw = 2)
plt.show()

Fig. 12 Lissajous figure (spiral)


Exercise: Generate various other kinds of Lissajous figures by varying
amplitudes and phases.

9.1.2 Polar Plot


# Polar plot 1

r = np.linspace(0, 1, 100)
theta = np.linspace(0, 4*np.pi,

100) plt.polar(theta, r, c = 'k', lw = 3)

Fig. 13 Ploar plot

# Polar plot 2 (Cardoid)


a, b = 1, 1
theta = np.linspace(0, 4*np.pi,

100) r = a + b*np.cos(theta)
plt.polar(theta, r, c = 'k', lw =

3) plt.xticks([0]) plt.yticks([0])

Fig. 14 Cardoid
# Polar plot 3 (Trigonometric function)
theta = np.linspace(0, 4*np.pi,

100) r = np.cos(theta)**2
plt.polar(theta, r, c = 'k', lw =

3) plt.yticks([])
plt.xticks([])
#plt.grid(False)

Fig. 15 Polar plot of trigonometric


functions
9.1.3 Plot Data from a File

Suppose, we write some x-y data in a simple text/data file:test.dat [Name and
extension do not matter.] and store it in some location. We need to call it
through the path name. The data may typically look like as follows:

#xy
1 2.0
2 4.2
3 5.1
4 9.6
5 3.8
6 2.2
# To load data (with NumPy)

>>> data =
np.loadtxt(‘C:/Users/Abhijit/Deskt op/test.dat’)

>>> type(data)
<class 'numpy.ndarray'>
>>> data
array([[1. , 2. ],

[2. , 4.2],
[3. , 5.1],
[4. , 9.6],
[5. , 3.8],
[6. , 2.2]])

# Extracting the columns


>>> x, y = data[:, 0], data[:, 1]
# To plot the loaded data

plt.plot(x, y, '-o', c = 'k') plt.xlim(0, 9)


plt.ylim(1, 10)
plt.text(6, 8.2, 'Peak Value',

color = 'k', size = 16) plt.xlabel(r'$\rho$', size = 20)


plt.ylabel(r'$\theta^2$', size =

20) plt.arrow(6, 8.5, -1.8, 1.1, length_includes_head = True, head_width


= 0.3) plt.show()

Fig. 16 Demonstration of how text and arrow are written inside a figure. The
labels (labels on x- and y-axes and inside the figure) can be represented by
Greek and Mathematical symbols.

Note:
• We can know about each of the functions xlim(), ylim(), text(), xlabel(),
ylabel(), arrow()by typing the name in help().
For example, type help(arrow)on interpreter.

• We can explore the default and keyword arguments by checking the online
manual. In short, the function text(position, label) writes text label inside the
figure at a position. The function arrow() draws an arrow from a point (x, y),
given by arrow(x, y, dx, dy) and so on.
• Also note that we can write Greek symbols and Mathematical expressions
in Labels as that is done in Latex. For example, we wrote r’$\theta^2$’ as a
raw string to produce ��2.

# Read data from a file (without NumPy)

x, y = [], []
for row in open('test.dat', 'r'): a, b = [eval(k) for k in

row.split()] x.append(a)
y.append(b)

9.1.4 Save Figure in a File plt.savefig(‘myfig.png’)


plt.savefig(‘myfig.pdf’)

Note: To save a figure, one may also look for some clickable option (often at
the bottom) on the figurewindow.

# Various options to save


f = plt.figure()
f.canvas.get_supported_filetypes()

{'ps': 'Postscript', 'eps': 'Encapsulated Postscript', 'pdf': 'Portable Document


Format', 'pgf': 'PGF code for LaTeX', 'png': 'Portable Network Graphics',
'raw': 'Raw RGBA bitmap', 'rgba': 'Raw RGBA bitmap', 'svg': 'Scalable
Vector Graphics', 'svgz': 'Scalable Vector Graphics', 'jpg': 'Joint Photographic
Experts Group', 'jpeg': 'Joint Photographic Experts Group', 'tif': 'Tagged
Image File Format', 'tiff': 'Tagged Image File Format'}

9.1.5 Greek Letters, Symbols, Math


To write Greek letters, symbolic or mathematical expressions (as texts and
labels) we must write between two Dollar symbols ($. . . $) with a back slash
(\) as they are incorporated fromTex/ LaTex type setting. The entire thing is
put inside two quotes (‘..’) preceded by ‘r’ to treat that as araw string.

# Writes�� = 0.5 inside fig plt.text(r’$\alpha$ = 0.5’)


# Writes �� > �� as title plt.title(r’$\psi$ > $\phi$’)

Note:
The backslashes(\), that are part of Tex/ LaTex

type setting, are also used inside Python strings. So, to avoid conflict, a raw
string (A python string preceded by r) is used. For example, we may use a
Python string where ‘\n’ creates a new line, ‘\t’ a new tab etc.

Lower-case Greek Letters:

�� \alpha, �� \beta, �� \gamma, �� \delta, �� \theta, �� \psi,


�� \phi, �� \epsilon, �� \eta, �� \mu, �� \nu, �� \rho, �� \pi,
�� \zeta, �� \xi, �� \sigma, �� \kappa, �� \tau

Upper-case Greek Letters:

∆ \Delta, Γ \Gamma, �� \Lambda, �� \Omega, �� \Sigma, �� \Theta,


�� \Phi, �� \Psi, �� \Pi, ℧ \mho, �� \nabla

Suffix and superfix:


����= \phi_n

����= \beta^k
����������= L^{ijkl}
��������= ��_{������} or A_{\rm{max}} [ \rm changes the
text into Roman}

Trigonometry:
sin �� = $\sin\theta$ cos �� = $\cos\pi$
Math Symbols:
∑���� \sum \x_i ∏���� \prod \x^k ∞ \infty ℏ \hbar

Math Expressions:
���� \frac{59}{86}����
(��) \left(\frac{5}{8}\right)��

√������ \sqrt{215} ��√�� \sqrt[5]{9} ��. ���� ± ��.


���� 9.81 \pm 0.02

# Examples of raw strings r’$\left(\frac{5}{8}\right)$’ r'$9.81 \pm 0.02$'



r’$\sum_{k=0}^\infty ∑ ���� x_k$’ ��=��
Fonts:

\mathrm{Python} \mathit{Python} \mathtt{Python}

‘Python’ in Roman font ‘Python’ in Italic

‘Python’ in Typewriter For detailed documentation on various symbols,


accents, and fonts, consult:
matplotlib.org/users/mathtext.html

9.1.6 Multiple Plots in One Figure

x = np.linspace(1, 10, 1000) y1 = np.sin(x)


y2 = np.sin(x**2)

# For two plots together


plt.plot(x, y1, x, y2)
plt.show()
Fig. 17 Two functions are plotted in a same figure.
# Multiple plots with styles

x = np.linspace(1, 10, 100)


lines = ['-', '--', ':']
legends = [r'$\nu = 1$', r'$\nu =

2$', r'$\nu = 3$'] for n in range(3):


plt.plot(x, np.sin((n+1)*x), lw = 2, linestyle = lines[n])

plt.legend(legends, fontsize = 16, loc = 'best')


plt.show()

Fig. 18 Multiple plots in the same figure with legends and line-styles
# Multiple plots with texts in Greek, Math
import matplotlib.pyplot as plt import numpy as np

x = np.arange(0, 2*np.pi, 0.01) f1 = 0.5*np.sin(x)


f2 = np.exp(-x)*np.cos(2*np.pi*x)

# c = 0 is darkest black
plt.plot(x, f1, x, f2, c ='0', lw
= 2) plt.xlim(0, 2*np.pi)
plt.text(2.0, 0.55,
r'$\frac{1}{2}\sin\theta$', size =

18) plt.text (3.5, 0.1, r'$\exp(x)\cos(2\pi x)$', size = 18) plt.show()

Fig. 19 Multiple plots with texts

With intertools module


In Python 3, there is an ‘itertools’ module

which can create various iterator functions such as count(), cycle(),


repeat()for efficient looping. These functions can operate on list or string.
For example, cycle() creates some ‘itertools cycle object’ on which next()
can operate to find the elements one by one, in cycle, and indefinitely.
Try the following on interpreter.

>>> from itertools import cycle >>> c = cycle([5, 10, 15]) >>> next(c)
5
>>> next(c)
10
>>> next(c)
15
>>> next(c)
5

>>> c = cycle('ABC') >>> next(c)


'A'
>>> next(c)
'B'

# Multiple plots: Use of intertools module

import numpy as np
from scipy.special import legendre import matplotlib.pyplot as plt from
itertools import cycle

x = np.arange(-1, 1, 0.01)
lines = ['.', '--', '-.',':', '-'] linecycle = cycle(lines)

for n in range(1, 6):


p = legendre(n)
plt.plot(x, p(x),

next(linecycle), lw = 2, color = 'black') plt.legend(['$P_1$','$P_2$',

'$P_3$', '$P_4$', '$P_5$']) plt.xlim(-1, 1)


plt.ylim(-1, 1)
plt.title('Legendre Polynomials',
size = '18') plt.show()

Fig. 20 Multiple plots in the same graph


9.1.7 Multiple Figures
x = np.arange(0, 2*np.pi, 0.01)
plt.figure(1)
plt.plot(x, np.sin(x))
plt.figure(2)
plt.plot(x, np.cos(x))
plt.show()

Note: Two figures are indexed as figure(1) and figure(2) with two
plt.figure() instructions. Instead of this, if we would write plt.figure() once
(or nothing), we would get two plots in the same figure as in previous
example. If we write, for example, plt.figure(‘My Graph’), ‘My Graph’ text
will appear on the top of the frame.

With this figure() function to precede with, we can create as many figures as
we want under this, with the same Python script. We may then add plots to
any of the figures or change just by referring the index or text in the
corresponding figure(). However, if we want to plot a single or multiple
figures in a same graph, we need not use figure() function.

9.1.8 Subplot (Multiple figures together)


For subplot, or multiple plots on the same figure, we need to use the
following function:
subplot(n, m, plot_number) [n = number of rows, m = number of columns]

We divide the frame into rows and columns and the third argument is the plot
number. For example, we write subplot(2, 2, 1) orsubplot(221), in short.
This indicates that we have divided the space into �� × �� plots and this
graph is on plot no. 1. The plot numbers are counted row wise, from left to
right.
221 222Subplot: Plot No. 1 – 4 in the 223 224 2× 2 matrix.
# Python Script for subplot
import matplotlib.pyplot as plt import numpy as np x = np.arange(0, 4,
0.01)
plt.subplot(221)
plt.plot(x, x**0.5*np.exp(-x), lw

= 2) plt.subplot(222)
plt.plot(x, np.sin(x**2), lw = 2)

plt.subplot(223)
plt.plot(x, x**2, lw = 2)
plt.subplot(224)
plt.plot(x, x**4*np.exp(-x**2), lw = 2) plt.show()

Note: The plots are indicated as 221, 222, 223, 224, the last digit is the plot
number and the first two digits are number of rows and number of columns.
Four different functions are plotted.

Fig. 21 Subplots
[Plot 1:��1/2��−��,Plot 2: sin ��2,Plot 3: ��2, Plot 4:��4��
−��2.]

9.1.9 Designing Grid Space

We can create Grid space with the module ‘gridspec’ from matplotlib. The
function GridSpec() creates grid layout to place subplots.

>>> import matplotlib.gridspec as grid


>>> g = grid.GridSpec(3, 3)
>>> ax1 = plt.subplot(g[:, 0])
>>> ax2 = plt.subplot(g[:2, 1])
>>> ax3 = plt.subplot(g[-1, 1])
>>> ax4 = plt.subplot(g[:, 2])
>>> plt.show()

Note: GridSpec(3, 3) created a 3×3 layout. Next four lines are creating
subplots by slicing the columns and rows of the space (as in done in NumPy
array slicing).

Fig. 22 An example of the layout of how grid space can be designed.

9.1.10 Statistical Plots


# Plot with Error bars

x = range(5)
y = [1, 4, 16, 25, 42]
plt.errorbar(x, y, fmt = 'o', xerr
= 0.2, yerr = 0.3, c = '0.1')

Fig. 23 Plotting with error bars


Histogram

For demonstration, Gaussian (Normal) random numbers are generated with


the module,random in NumPy. The hist() function in pyplot creates a
histogram out of those numbers. The plt.hist() returns a tuple of two arrays:
the first array consists of the counts or frequencies and the second array
consists of the division of bins. For example,

>>> x = [0.2, 1, 0.4, 0.8, 0.5, 1.1]


>>> plt.hist(x, bins = 3)
(array([2., 1., 3.]), array([0.2,
0.5, 0.8, 1.1]), <a list of 3 Patch
objects>)

# Plot Histogram
x = np.random.normal(1, 4, 100000) plt.hist(x, bins = 40, rwidth = 0.8,
histtype = ‘bar’) plt.show()

Note: bins = 40 indicates that 40 bins are created;rwidth = 0.8 indicates the
relative width of the bars as a fraction of bin width. Default histogram-type is
with bars. If we write, histtype = ‘step’, it will show the outline only. There
are many keyword arguments in hist() that can be checked with help(plt.hist)
which may be used to control the display.

Fig. 24 The histogram created out of the Normal random numbers.

# Histogram with different style plt.style.use(‘ggplot’)


plt.hist(x, bins = 40, color = '0.1', rwidth = 0.8, histtype = 'step', lw = 2)

Fig. 25 Histogram plotted with different style. The keyword, histtype = ‘step’
plots the outline only.

Note: There are many keyword arguments that can control the histogram. For
example, keyword, normed = 1 will normalize the distribution.

# Stacked Histograms

x = np.random.randn(10000, 3) colors = ['blue', 'green', 'red'] plt.hist(x,


bins = 30, color = colors, stacked = True, edgecolor
= 'black')

Note: With random.randn(10000, 3), three different arrays of 10000 Normal


random numbers are creatd. The display will consist of colorful histograms
stacked on top of another. The edge color of each distinguishability
edgecolor. bar is made black for

through the keyword

Fig. 26 The display of 3 histograms stacked in a way. This monochrome


image was created with shades of gray:colors = [‘0.3’, ‘0.5’, ‘0.8’]

Stack plot

import pandas as pd
from numpy.random import uniform as u data = {'Kolkata': u(0, 1, 20),
'Delhi': u(0, 1, 20), 'Mumbai': u(0, 1, 20)} index = range(20)

# Data as DataFrame
D = pd.DataFrame(data, index) print(D.head())

Kolkata Delhi Mumbai


0 0.384820 0.919648 0.814143
1 0.442323 0.604442 0.913621
2 0.113300 0.137161 0.807373
3 0.645376 0.088518 0.841654
4 0.832507 0.092948 0.869621

y = np.vstack([D['Kolkata'],

D['Delhi'], D['Mumbai']]) x = range(20)


labels = ['Kolkata', 'Delhi',

'Mumbai'] #colors = ['red', 'tomato', 'skyblue'] colors = ['0.25', '0.50',


'0.75'] plt.stackplot(x, y, labels = labels, colors = colors, edgecolor =
'black') plt.legend(loc = 2)
Fig. 27 Stack plot
Bar plot
# Ordinary Bar plot
positions = [0, 1, 2, 3, 4, 5]

heights = [2, 10, 3, 1.5, 6, 0.8] plt.bar(positions, heights, color = '0.4')


plt.show()
Fig. 28 Bar plot
# Decorated Bar plot

labels = ['First', 'Second', 'Third']


heights = [20, 10, 5]
bars = plt.bar(labels, heights, color = '0.8', width = 0.8)
bars[0].set_hatch('/')
bars[1].set_hatch('*')
bars[2].set_hatch('o')
plt.show()
Fig. 29 Bars with decoration
# Bar plot with two sets of data men = [80, 50, 75, 60]
women = [90, 62, 50, 65]

# Bar positions
x = np.arange(len(men))
w = 0.4 # Width plt.bar(x, men, width = w, label =

'Men', color = '0.1') plt.bar(x + w, women, width = w, label = 'Women',


color = '0.7') plt.legend(fontsize = 14)
Fig. 30 Different colored bars plotted.
Pie Chart

Given a list of numbers (percentages for a set of items), the pie chart can be
made by plt.pie() function.

>>> x = [10, 05, 25,60] >>> plt.pie(x)

As arguments of pie(), we can set corresponding labels, colors in the form of


list. We can also provide additional attributes like exploding a slice of pie
(‘explode’) and auto percentages (‘autopct’) of the corresponding slices for
visual treat.

# Python Script for Pie diagram slices = [43, 20, 12, 9, 16] colors = ['0.7',
'0.3', '0.6',

'0.4', '0.5'] labels = ['Rice', 'Sugar', 'Wheat', 'Cotton', 'Jute'] ex = (0, 0, 0,


0.1, 0)

plt.pie(slices, labels = labels, explode = ex, shadow = True, autopct =


'%1.1f%%', colors = colors) plt.show()

Fig. 31 Pie diagram


9.1.11 Contour Plot

A contour plot is a 2D projection of a 3D plot. It can be created by


contour()function. The 3 arguments ofcontour() function: a grid ofxvalues, a
grid ofy-values and a grid ofz-values. The x & y values represent position on
the plot and z-value represent the contour level (height). One way to prepare
the grid ofx & y values is by using

meshgrid(x, y) function taken from Numpy which prepares a 2D grid from


one dimensional arrays.

# Preparation of Grid x = np.linspace(0, 10, 10) y = np.linspace(0, 10, 10)


X, Y = np.meshgrid(x, y)

plt.plot(X, Y, '.', ms = 12, c = 'k')


plt.title('X-Y Grid', size = 20)
plt.show()

Note: The meshgrid() function prepares a 2D grid on the x-y plane. In this
case, 10 discrete points each along x- and y-axes.
Fig. 32 The X-Y Grid

For contour plot, we need to prepareZ(X, Y) from the grid, and


plotcontour(X, Y, Z).
# Contour plot

x = np.linspace(0, 10, 100)


y = np.linspace(0, 10, 100)
X, Y = np.meshgrid(x, y)
Z = np.sin(X)**2 + np.cos(Y)**2 plt.contour(X, Y, Z, colors =

'black')
Fig. 33 Contour plot
# Contour map: another example

f = lambda x, y: np.sin(X)**3 + np.cos(Y) * np.sin(X)


x = np.linspace(0, 5, 100)
y = np.linspace(0, 5, 100)
X, Y = np.meshgrid(x, y)
Z = f(X, Y)
plt.contour(X, Y, Z, 100, cmap = 'binary')
Fig. 34 Contour plot with binary mapping

Note: In the arguments of the contour(X, Y, Z, 100, cmap = ’binary’)


function, the number 100 is to control the density of contours.

# Contour plot of continuous type

x = np.linspace(0, 1, 100)
y=x
X, Y = np.meshgrid(x, y)
Z = 2 * np.sin(np.pi*X) *
np.sin(np.pi*Y)

plt.contourf(X, Y, Z, cmap = 'gray')


plt.show()

Fig. 35 Contour plot of some other kind with contourf() function. This
function draws the contour lines and fills the contours.

Contour plot with Data


# With created data to check import numpy as np
import scipy.interpolate as si import matplotlib.pyplot as plt

# Data created by random number generators for testing


x = np.random.uniform(0, 1, 100) y = np.random.uniform(0, 1, 100) z =
np.random.randn(100)

# X and Y Grid values


xi = np.linspace(x.min(), x.max(),

100) yi = np.linspace(y.min(), y.max(),


100) zi = si.griddata((x, y), z, (xi[None, :], yi[:, None]), method = ‘cubic’)
plt.contour(xi, yi, zi, cmap = 'gray')
Fig. 36 Contour plot with (x, y, z) data Note: xi[None, :], yi[:, None] are to
convert 1D arrays into 2D arrays.

With downloaded data


# Example 1

We can open a downloaded spread sheet (Excel file) with the csv file reader
from Pandas package and proceed.

import pandas as pd
D=
22.5 22.2 21.6 20.8 19.3
0 17.0 16.3 14.4 12.9 12.0
1 12.0 12.0 13.2 13.2 15.7
2 16.1 15.6 15.0 14.7 14.6
3 14.8 15.6 15.0 12.4 13.4
4 13.0 12.8 12.0 11.9 11.7
pd.read_csv(r"C:/Users/Abhijit/ Desktop/data_test.csv") D.head() #
Head of DataFrame

# Extract columns into 1D arrays # DataFrame to NumPy array


d = D.to_numpy()

x = d[:, 1] # First three columns y = d[:, 2]


z = d[:, 3]
# Data to Grid form for contour plotting

N = x.size
xi = np.linspace(x.min(), x.max(),

N) yi = np.linspace(y.min(), y.max(),
N) zi = si.griddata((x, y), z, (xi[None, :], yi[:, None]), method = ‘cubic’)
plt.contour(xi, yi, zi, cmap = 'gray')

Fig. 37 Contour plot with downloaded data Note: The data file can be read
with an appropriate file reader. For a plain text/ data file, we can use loadtxt()
in NumPy. However, for a spread sheet (an Excel file, for example) which
can be saved and treated as CSV (Comma Separated Values) file, can be read
through csv file reader, read_csv() in pandas module. The file read by
pd.read_csv() is a type which is known as DataFrame (a form of structured
data). Next, we converted a DataFrame into a NumPy array. Example #2

In this demonstration, data from a url are directly accessed and plotted. [The
following demonstration is ofjupyter notebook style.]

In [1]:

url =
'https://raw.githubusercontent.com /alexmill/website_notebooks/master
/data/data_3d_contour.csv'
data = pd.read_csv(url)
type(data)
Out[1]:
pandas.core.frame.DataFrame

In[2]:
data.tail()

Out[2]:
xyz
51 0.35 1.0 −0.45
52 0.50 1.0 −0.46 53 0.69 1.0 −0.48 54 0.85 1.0 −0.47 55 1.0 1.0 −0.49

In[3]:
table =
data.pivot_table(index='x', columns='y', values='z')

Note: pd.pivot_table() returns spread-sheet style table as a DataFrame. The


columns and rows will be in hierarchical orders. The output may be inspected
to understand what a pivot table looks like. One can check table.values(),

table.values.max() ,
table.values.min(), table.values.shape etc.

Check unique values of xand y-data: data.x.unique(), data.y.unique()


In[4]: x_unique =
np.sort(data.x.unique())

y_unique =
np.sort(data.y.unique())
X, Y = np.meshgrid(x_unique, y_unique)
Z = table.values.T

In [5]:
import matplotlib.pyplot as plt import matplotlib.cm as cm
fig = plt.figure()
ax = fig.add_subplot(111)

levels = np.array([-0.4, -0.2, 0, 0.2, 0.4])


cpf = ax.contourf(X, Y, Z,
len(levels), cmap = cm.gray)

line_colors = ['black' for l in cpf.levels]


cp = ax.contour(X, Y, Z,
levels=levels, colors = line_colors) ax.clabel(cp, fontsize=14,
colors=line_colors)

Fig. 38 Contour
plot with data from spread-sheet.
9.2 3D Plot

Once we prepared XY-grid through meshgrid() function, we need to import


3D plotting modules from the matplotlib toolkits to create a 3D plot.

from mpl_toolkits.mplot3d import Axes3D from matplotlib import cm


# Fig frame is prepared fig = plt.figure()

# Axes are prepared


ax = Axes3D(fig)
ax.plot_wireframe(X, Y, Z, color =
'k')

Fig. 39 The 3D plot (wireframe structure)


# Colormap options for continuous surface

# ax.plot_surface(X, Y, Z, cmap = cm.coolwarm)


ax.plot_surface(X, Y, Z, cmap = cm.gray)
Fig. 40 3D plot with continuous surface (Gray mapping)

# Python Script for 3D plot (another way)


import numpy as np
import matplotlib.pyplot as plt from mpl_toolkits import mplot3d

x = np.linspace(-10, 10, 100) y = np.linspace(-10, 10, 100)


X, Y = np.meshgrid(x, y)
Z = np.sin(np.sqrt(X**2 + Y**2))

fig = plt.figure()
ax = plt.axes(projection = '3d') ax.contour3D(X, Y, Z, 80, cmap =
'binary') plt.show()

Fig. 41 3D contour plot


Note: There are many options for color maps (cmap = color map) for good
representation in 3D colorspace
[See: matplotlib.org/2.0.2/users/colormaps.html].

The number 80 as argument is for density of points. Also, there are other
keyword arguments which can be manipulated to create a 3D plot of choice.
We can create 3D scatter plot with ax.scatter(). A 3D triangulated surface can
be created with ax.plot_trisurf().

3D Bar Plot
fig = plt.figure()
ax = fig.add_subplot(111,
projection = '3d')

xpos = np.arange(1, 11)


ypos = random.randint(1, 10, 10) zpos = np.zeros(10)
dx = np.ones(10)
dy = np.ones(10)
dz = np.linspace(1, 100, 10) # ax.bar3d(xpos, ypos, zpos, dx,

dy, dz, color = '#32CD32') ax.bar3d(xpos, ypos, zpos, dx, dy, dz, color =
'0.3') plt.show()

Fig. 42 3D Bar plot


Note:
• Once a 3D figure is displayed, one can hold the figure with the computer
mouse and rotate the figure in any direction to check.

• Colored contours and 3D colored surfaces may be generated through cmap


(Color map) parameter. Classified items with tutorials and examples are
given in the matplotlib surface plot documentation section (matplotlib
website) and elsewhere.

Additional Note:

If x- and y-arrays are available in the form that they correspond to the
coordinates of all the grid points, then we can readily plot them without
invoking meshgrid(). Only, criterion is that the x, y, z arrays are to be
reshaped into 2D arrays. For example,

x = [0, 0, 0, 1, 1, 1, 2, 2, 2] y = [0, 1, 2, 0, 1, 2, 0, 1, 2] z = [5, 6, 9, 1, 2, 3, 5,


9, 11] X = np.reshape(x, (3, 3))
Y = np.reshape(y, (3, 3))
Z = np.reshape(z, (3, 3))
fig = plt.figure()
ax = fig.add_subplot(111, projection = '3d')

ax.plot_surface(X, Y, Z)
plt.show()
Fig. 43 Surface plot demonstration with arranged data
Exercises:

1. Given a set of (��, ��) data in the form of a dictionary: {1:0.5, 2:3.8,
3:7.9, 4:16.5, 5:27.5}. Plot�� against�� with

��-label as ‘Time’ and y-label as ‘Temperature’.

2. Given a function, �� = ��(��)= 2��3exp (−��2), generate some


(��, ��) data points for the ��range (0, 20), then plot with matplotlib
package. Also store data in a file and later plot with matplotlib.
3. Usematplotlib to produce a plot of the functions ��(��) = �� −
��/10 ������(����) and ��(��) = ���� − ��/3 over
the interval [0, 10]. Include labels for the��- and ��-axes, and a legend
explaining which line is which plot. Save the plot as a .jpg (“Jpeg”) file.

4. Read a 2 column data file ‘mydata.d’. (a) Store the values in the array X
and Y, (b) Fit the data by the function �� = �� + �� , (c)��2
Report��, ��, (d) Plot the original data and the fitted curve superposed.

5. Generate a numpy array X within [0, 5]. Plot the following curves using
matplotlib for i) �� = (3��3− 2��4)/ (1 + ��2), ii) �� = sin2��,
iii) �� = ��1exp(−��1��)+ ��2exp ( −��2��) for ��1,
��2, ��1, ��2= [1, −1, 1, 2], [1, −1, 2, 1], [1, 10, 1, 1] and superpose
them with putting legends.

Enthusiastic readers may explore an amazing visualization module seaborn


which is built on top of matplotlib. This provides high level
drawing attractive and
statistical plots.
interface for

informative

CHAPTER

10
SymPy: Symbolic Python

“ In my perception, the world wasn't a graph or formula or an equation. It


was a story.” – Cheryl Strayed

SymPy is Symbolic Python. We do symbolic calculations (as are done with


pen and paper) with this. The output symbols. [For is returned in
mathematical
module references and documentation, consult:
docs.sympy.org/latest/modules.]

In this brief chapter on SymPy, we try to introduce some essential ideas and
features. SymPy is a huge independent library. Many important mathematical
projects are developed based on this. The readers may explore them as and
when needed for their work. The following (inside the box) is from SymPy
documentation in

www.sympy.org/en/index.html.
About

‘SymPy is a Python Librar y for symbolic mathematics. It aims to become a


full-featured computer algebra system (CAS) while keeping the code as
simple as possible in order to be comprehensible and easily extensible.
SymPy is written entirely in Python.’

# Import everything from SymPy >>> from sympy import *


# Check the directory >>> dir(sympy)
# For fancy printing
>>> init_printing()

Note: It is not a good practice to import everything in namespace. But here


we imported everything to make the next instructions appear minimal, for
demonstration purpose.

10.1 Symbols, Expressions, Calculations


# Symbolic representations >>> pi
√��
>>> sqrt(3)
√3
>>> a = sqrt(3)

# Evaluate in floating point number >>> a.evalf()


1.73205080756888
>>> cos(pi)
−1
>>> acos(-1)
��

# Algebraic Expressions # Define symbols

>>> x, y, z = symbols('x, y, z') >>> y*exp(x) + z**2


������+ ��2

>>> a, b = 19, 38 >>> Rational(a, b) # Ratio


1
2

>>> x**Rational(2, 3)
��2/3
# Expressions with trigonometric functions, Greek symbols

>>> a, b, c = symbols('alpha, beta, gamma')


>>> sin(a) + log(b**c)/sqrt(b)

sin
(��)+
log(����)
√��
# Simplification of Mathematical expressions

>>> x, y = symbols('x, y')


>>> a = x**3 +2*x**2 + 5*x
>>> b = 2*x**3 - 2*x**2 + 4*x >>> c = simplify((b - a)/(b + a)) >>> c
��2− 4�� − 1

3(��2+ 3)
# Evaluating expression
# Substitue 1 in place of x
>>> c.subs(x, 1)
−1
3
# Multiple substitutions

# Many symbols
>>> x, y, z = symbols('x, y, z') >>> a = x**2 + x*y + z
>>> a.subs([(x, 2), (y, -1), (z,

1)]) 3
# Numerical evaluation of a builtin function
>>> x = Symbol('x')
>>> f = lambdify(x, sin(x),

'math') >>> f(0.5)


0.479425538604

Note: lambdify() converts the function sin() taken from math module.
# Simplification of trigonometric and other expressions

>>> simplify(sin(x)**2 +
cos(x)**2)
1
>>> simplify(4*sin(x)*sin(pi/3 – x)*sin(pi/3 + x))
sin(3��)

# Trigonometric simplifications by trigsimp()

>>> trigsimp(4*sin(x)*sin(pi/3 – x)*sin(pi/3 + x))


sin(3��)

# Factor cancellation

>>> cancel((x**2 + 2*x + 1)/(x**2 + x))


�� + 1
��

# Factorization

>>> x = symbols('x') >>> f = x**2 - 5*x + 6 >>> factor(f)


(�� − 3)(�� − 2)

# Expansion
>>> expand(x*(2*x+3))
2��2+ 3��
>>> expand((cos(x) + sin(x))**2) sin2(x)+ 2sin(x)cos(x)+ cos2(x)

# Trigonometric Expansion

>>> expand_trig(sin(x+y)) sin(x)cos(y)+ sin(y)cos(x)


>>> expand_trig(cot(x+y)) cot(x)cot(y)− 1

cot(x)+ cot(y)
# Rewriting Trigonometric expression
# tan(x) written in terms of sin(x) >>> tan(x).rewrite(sin)
2sin2(x)
sin(2x)
# Factorial, Binomial combination

>>> n, k = symbols('n, k')


>>> factorial(n)
��!
>>> binomial(n, k)

(����)
# Simplification of combinatorial
>>> combsimp(binomial(n, n
1)/binomial(n, n-2)) 2
�� − 1
# Beta, Gamma function, Hypergeometric function

>>> beta(x, y) # Beta function B(x, y)


>>> beta(x, 1)
1

��
>>> gamma(z) # Gamma function Γ(��)
# Hypergeometric function >>> hyper([1,2], [3], z) ��12(1, 23) |��
>>> hyperexpand(hyper([1,2], [3], z))
−2��−2log(1 − ��)

Note: SymPy contains all kinds of special functions, Dirac Delta function,
Heaviside function, class of Singularity functions, all kinds of Gamma
functions (digamma, trigamma, polygama), error integral, Fresnel integral,
Sine integral, logarithmic integral, elliptic integral and many more.

# Infinite series: Taylor Series Expansion around x0 = 0


>>> exp(x).series(x, x0 = 0)
1 + �� +��
2345
2+��6+��24+ ��120+ ��(��6)
>>> log(x).series(x, x0 = 2)
log( 2) − 1 −(�� − 2)2 +(�� − 2)3 4 8 24 −(�� − 2) +(�� − 2)
5
160 +��
+ ��((�� − 2)6; �� → 2)
# Declare function and expand

>>> f = Function('f') >>> f = sin(x)/x


>>> f.series(x, x0 = 0)

1 −��
24
6+ ��120+ ��(��6)
# Algebraic equation, solve

>>> Eq(x**2 - 5*x + 3, 0) ��2− 5�� + 3 = 0


>>> solve(x**2 - 5*x + 3) # Root finding

[5−√13 , 5+√13 ]2 2 2 2
# System of equations, Solve

>>> x, y = symbols('x, y')


>>> solve((x + y - 4, x - y - 2), x, y)
{�� ∶ 3, �� ∶ 1}

10.2 Matrix
# Column Matrix
>>> Matrix([1, 2, 3])
1
[2]
3
>>> Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
123
[4 5 6]
789

Matrix operations:

>>> A = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]])


>>> B = Matrix([[1, 0, 1], [1, 2,
-1], [3, 1, 4]])

# Shape, Rows, Columns


>>> A.shape
(3, 3)

>>> A.row(1) # Row 1 [4 5 6]


>>> A.col(0) # Column 0

1
[4]
7

# Addition
# Addition of two matrices >>> A + B

224
[575]
10 9 13

# Product

>>> A * B 12 7 11
[27 16 23] 42 25 35

# Inverse of Matrix >>> B**-1


9
4 4 −1
−71 1
442
[
−5
4 −1
1
2]

>>> B*B**-1 # To check 1 0 1


[0 1 0]
001

# Transpose

>>> A.T
147
[2 5 8]
369

# Power of Matrix

>>> A**2
30 36 42
[ 66 81 96 ]
102 126 150
# Determinant

>>> A.det()
0
>>> B.det()
4

# Eigenvalues, Eigenvectors >>> B.egenvals() # Eigenvalues >>>


B.eigenvects() # Eigenvectors
# Diagonalization of Matrix The method diagonalize()returns a tuple (P, D)
where D is diagonal and D = P−1BP.
>>> P, D = B.diagonalize() >>> D
200
0 52−√17 0
√172 +5
[0 0 2]
>>> P
1 −√174 −34 −34+√17
−5 3
[ 4+√17 4−√17]1 1 1
# Special Matrices >>> diag(3, 4, 1) # Diagonal Matrix

300
[0 4 0]
001
>>> zeros(3) # Null Matrix
000
[0 0 0]
000
>>> ones(2, 3)

[1 1 1
1 1 1]

>>> eye(3) # Identity Matrix 1 0 0


[0 1 0]
001

10.3 Calculus
10.3.1 Limit
>>> Limit(sin(x)/x, x, 0)

lim(sin(x)
x→0+ x)

>>> f = Limit(sqrt(x**2+1)/x, x, oo) [ oo is infinity.]


2+ 1
lim(√��
x→∞ x)

# Evaluate the limit >>> f.doit()


1

# Direct evaluation
>>> limit(sin(x)/x, x, 0) 1

10.3.2 Differentiation >>> y = Function('y') >>> t = symbols('t')

# Differentiate y w.r.t. t >>> y(t).diff(t) ��

������(��)
# Substitute t = 0
>>> y(t).diff(t).subs(t, 0)
��
������(��)|��=0
# Differentiate twice w.r.t. t >>> y(t).diff(t, t) ��2
����2��(��)

>>> y = t**3 + 2*t**2 + 4 >>> y.diff(t)


3��2+ 4��

>>> y.diff(t).subs(t, 1) 7
Also, one can write: >>> diff(y, t)
3��2+ 4��

# Differentiate w.r.t. x >>> diff(sin(x)*exp(x), x) ����sin(��)+


����cos (��)

10.3.3 Integration
# Indefinite Integration >>> integrate(exp(x)*sin(x))
����sin (��)−����cos (��) 2 2

# Representation of definite Integral

>>> Integral(sin(x)*exp(x), (x, 0, 1)) 1


∫ ����sin(��)����
0
# Evaluating a definite Integral >>> integrate(exp(x)*sin(x), (x, 0, 1))
−��cos(1)2 +12+��sin(1)

>>> integrate(x**2*exp(-x**2), (x, 0, oo))


√��
4

# Double Integral
>>> x, y = symbols('x, y')
>>> Integral(exp(- x**2 - y**2), (x, 0, oo), (y, 0, oo))
∞∞
∫ ∫ ��−��2−��2��������
00

>>> integrate(exp(- x**2 - y**2), (x, 0, oo), (y, 0, oo))


��
4

10.4 Plotting a Function

Once we define a symbol and make a function through lambdify(), we can


usematplotlib library to plot. However, there are plotting module in SymPy
based on Matplotlib which we can readily use.

10.4.1 Plot with Matplotlib

x = Symbol('x')
f = lambdify(x, sin(x**2),'numpy') import numpy as np
import matplotlib.pyplot as plt
xvals = np.arange(0, 10, 0.01) yvals = f(xvals)
plt.plot(xvals, yvals)
Fig. 1: Plotting function with lambdify and using Matplotlib library.
Note: The function sin() was taken from NumPy.

10.4.2 Plot with SymPy Plotting Library

The plotting module allows us to make to make dimensional and 3-


dimensional plots. Presently, the plots are rendered usingMatplotlib as a
backend.

>>> x = Symbol('x')
# Directly plots the expression >>> plot(x**2)
Fig. 2: Plotting 2D with SymPy plot
>>> x = Symbol(‘x’)
# Plots Mathematical function
>>> plot(sin(x)/x)
Fig. 3: Plotting 2D with SymPy plot

Note: The function plot() is from sympy.plotting module. We can write:from


sympy.plotting import plot. Here we have imported everything at the
beginning, so we don’t need to write. The default plot is with blue line, in the
domain:[−10, 10].

Online help on plot:


plot(*args, show=True, **kwargs) Plots a function of a single variable as a
curve.
The Boolean keyword argument show = True will plot the graph
immediately. We can setshow = False when we create the plot but intend to
show later.

The online help and the Plotting Function Reference page in the SymPy
documentation shows various arguments through which we can control the
plot: ‘line_color’, ‘title’, ‘label’, ‘xlabel’, ‘ylabel’, ‘xscale’, ‘yscale’,
‘axis_center’, ‘xlim’, ‘ylim’, ‘annotations’, ‘markers’, ‘rectangles’, ‘fill’,
‘adaptive’, ‘depth’,
‘nb_of_points’, ‘size’ etc.

# Control x-scale
>>> plot(x**2*exp(-x), (x, 0, 8))

Fig. 4: Plotting 2D with SymPy plot


# Many plots in a Graph
>>> plot(sin(x), cos(x))
Fig. 5: Plotting 2D with SymPy plot. Many plots together.
# Collect plots, show later

>>> p1 = plot(x**2*exp(-x), (x, 0, 5), show = False)


>>> p2 = plot(abs(sin(x)), (x, 0, 5), show = False)
>>> p1.append(p2[0])
>>> print(p1)
Plot object containing:
[0]: cartesian line: x**2*exp(-x)
for x over (0.0, 5.0)
[1]: cartesian line: Abs(sin(x))
for x over (0.0, 5.0)
>>> p1.show()

Note: plot() creates a tuple. The 0-th element is the plot. So, we took p2[0] to
append.
Fig. 6: Plotting 2D with SymPy plot. Merge plots and show.

# Parametric plot (in 2D)


plot_parametric(sin(x), cos(x))
Fig. 7: Parametric plot
t = Symbol('t')
plot_parametric(2*sin(t+3/2*pi) + 4/3*cos(3*t), 2*cos(t + 3/2*pi) +
4/3*sin(3*t), (t, -2*pi, 2*pi))
Fig. 8: Parametric plot
3D Plot

from sympy.plotting import plot3d x, y = symbols('x y')


plot3d(cos(x)**2 + sin(y)**2,

(x, -pi, pi), (y, -pi, pi))


Fig. 9: Plot in 3D
# Parametric plot in 3D

from sympy.plotting import


plot3d_parametric_line
t = Symbol('t')
plot3d_parametric_line(cos(t), sin(t), t)
Fig. 10: Parametric plot in 3D

10.5 Geometrical Objects


# Point and Distance

>>> from sympy import symbols, init_printing


>>> import sympy.geometry as g
>>> x, y = symbols('x, y')
>>> init_printing()
>>> p = g.Point(0, 0)
[A point is created at (0, 0).]
>>> p
Point2D(0, 0)
# Point at (x, y)
>>> p1 = g.Point(x, y)

# Distance between p1 and p >>> p1.distance(p)


√��2+ ��2

# Triangle (a, b, c)

>>> a, b, c = g.Point(0, 0), g.Point(4, 1), g.Point(-1, 2)


>>> t = g.Triangle(a, b, c)

# Area of the triangle >>> t.area


9/2

# Ellipse
(center, hradius, vradius)
>>> e = g.Ellipse((0, 0), 5, 2) >>> e.area
10.��
# Parameters of Ellipse

>>> e = g.Ellipse((1, 0), hradius = 5, eccentricity = 0.7)


>>> e.vradius
3.57071421427142
>>> e.apoapsis
17/2
>>> e.circumference

20.E(49 )100
>>> e.euation(x, y)
0.078..��2+ (��−1)2− 15 5

10.6 Solving Differential Equations First order:������+


����(��)= ��������

>>> m, g, k = 1, 9.8, 1
>>> t = Symbol('t')
>>> v = Function('v')
>>> sol = dsolve(Eq(m*v(t).diff(t)

+ k*v(t), m*g), v(t)) >>> sol # Solution ��(��)= ��1��−1.0��+ 9.8

>>> sol.lhs # Left hand side ��(��)


>>> sol.rhs # Right hand side ��1��−1.0��+ 9.8

# Apply initial condition, find ��1 Let us substitute ��(0)= 0.

>>> C1_value =
solve(sol.rhs.subs(t, 0))[0]
>>> C1_value # Value of ��1
-9.8

# Substitute the value of ��1, find solution

>>> C1 = Symbol('C1')
>>> sol = sol.subs(C1, C1_value) >>> sol
��(��)= 9.8 − ��1��−1.0��

# Plot the solution

>>> from matplotlib import pyplot as plt


>>> import numpy as np
>>> func = lambdify(t,
sol.rhs,'numpy')
>>> xvals = np.arange(0, 10, 0.1)
>>> yvals = func(xvals)
>>> plt.plot(xvals, yvals)
Fig. 11: Plotting thorugh lambdify
Second order:��2��+ ��(��)= 0����2
# Form the Equation

>>> t = Symbol('t')
>>> y = Function('y')
>>> equation = Eq(y(t).diff(t,t) +

y(t), 0) >>> equation


��(��)+��2
����2��(��)= 0
# Solve the Equation

>>> sol = dsolve(equation, y(t)) >>> sol


��(��)= ��1sin(t)+ ��2cos(t)

# Find constants, ��1 and ��2 by initial conditions

# Derivative of solution
>>> y1 = sol.rhs.diff(t)
>>> y1 # ��′ ��(��)= ��1cos(t)− ��2sin(t)

>>> sol.rhs.subs(t, 0)
��2
>>> sol.rhs.diff(t).subs(t, 0) ��1

# Take the constants and put into solution.


>>> C1, C2 = symbols('C1, C2')
>>> s = solve((sol.rhs.subs(t, 0)

- 0, sol.rhs.diff(t).subs(t, 0) - 1), C1, C2) >>> s # Returns as a Dictionary


{��1: 1, ��2: 0}

Note: ‘sol.rhs.subs(t, 0) – 0’ indicates ��(0)= 0 and


‘sol.rhs.diff(t).subs(t, 0) – 1’ indicates ��′(0) = 1.

# Unpack the constants, substitute into solution


>>> C1_value, C2_value = s.values() >>> sol = sol.subs({C1:C1_value,

C2:C2_value}) >>> sol # Final solution ��(��)= sin (��)

# Plot the solution


>>> import numpy as np

>>> import matplotlib.pyplot as plt


>>> func = lambdify(t, sol.rhs)
>>> x = np.arange(0, 10, 0.01)
>>> y = func(x)
>>> plt.plot(x, y)
Fig. 12: Plotting
the solution

CHAPTER

11
Pandas: Structured Data
“All good art is abstract in its structure.” – Paul Strand

We often receive data as spread sheets or as various other tabular forms. The
external module Pandas has mechanism to handle such structured data.

Pandas deal with the following three data structures:


• Series
• DataFrame
• Panel
These data structures are built over NumPy arrays. We must install pandas
over core Python before we import the module. The installation procedure is
the same as that with other external packages like NumPy, SciPy etc.

>>> import pandas as pd >>> import numpy as np


11.1 Series
# Data
>>> x = np.arange(10, 50, 10) >>> pd.Series(x)
0 10
1 20
2 30
3 40
dtype: int32

Note: The row indices: 0, 1, 2, 3 are automatically given. So, we form a


structure out of the 1D array. The data-type is by defaultint32.

# Imposing index

>>> index = ['a', 'b', 'c', 'd'] >>> s = pd.Series(x, index) >>> s
a 10
b 20
c 30
d 40
dtype: int32

# Searching through index

>>> s[0]
10
>>> s[‘a’]
10

# Methods and attributes


>>> s.axes

[Index(['a', 'b', 'c', 'd’], dtype = 'object’)]


>>> s.values
array([10, 20, 30, 40])
>>> s.size
4
>>> s.shape
(4,)
>>> s.ndim
1
>>> s.dtype

dtype(‘int32')
# New row added

# New row added with index ‘e’ >>> s['e'] = 50 >>> s


a 10
b 20
c 30
d 40
e 50
dtype: int64
# Note: dtype changed

# Data with strings

>>> data = [‘Raman', ‘Guido', ‘Ismail', ‘Apu']


>>> pd.Series(data)
0 Raman
1 Guido
2 Ismail
3 Apu
dtype: object
# Data as scalar (a single data)

>>> index = [‘a’, ‘b’, ‘c’, ‘d’] >>> pd.Series(10, index, int) a 10
b 10
c 10
d 10
dtype: int32

# Series from a dictionary

>>> data = {'a':10, 'b':20, 'c':30, 'd':40}


>>> pd.Series(data)
a 10
b 20
c 30
d 40
dtype: int64

# New index forced

>>> index = ['a', 'b', 'c', 'd', 'e']


>>> pd.Series(data, index)

a 10.0
b 20.0
c 30.0
d 40.0
e NaN

# No data corresponding to this index dtype: float64

Note: NaN = Not a Number. This is Python’s way of putting this object
whenever there is a missing data or misunderstood data.

Mathematical operators, built-in functions, and all NumPy methods can be


applied overSeries as that are done over NumPy arrays. Also, indexing,
slicing and other mechanisms can be applied in the same way as those are
done for NumPy arrays. This is possible because Series() in Pandas is built
overarray() in NumPy. For example, ifS is the Series, we may check S**2,
sum(S), max(S), np.sqrt(S), S.mean(), S[1], S[::2]

etc. in the same way when we deal with a NumPy array.

11.2 DataFrame
# Argument is a 1D list/ array

>>> x = [10, 20, 30, 40]


>>> pd.DataFrame(x)

0
0 10
1 20
2 30
3 40

Note: The tabular structure is created with default row index: 0, 1, 2, 3 and
column index: 0.
# With 2D list as argument

>>> x = [[10, 20, 30, 40], [50, 60, 70, 80]]


>>> pd.DataFrame(x)
0123
0 10 20 30 40

Note: The tabular form with row index: 0, 1 and column index: 0, 1, 2, 3.
# With row and column names

>>> index = [‘a’, ‘b’]


>>> columns = ['A', 'B', 'C', 'D'] >>> d = pd.DataFrame(x, index,

columns)
# DataFrame ‘d’ is created >>> d

ABCD
a 10 20 30 40
b 50 60 70 80

# Calling by row index, columns

>>> d[‘A’]
a 10
b 50
>>> d[‘A’][‘a’]
10

Methods and Attributes forDataFrame:

d.axes, d.size, d.ndim, d.T, d.empty, d.values, d.head(), d.tail(), d.sum(),


d.mean(). d.std(), d.max(), d.describe()

We can test them on interpreter (or look up the online help). For example,
>>> d.axes
[Index(['a', 'b'],
dtype='object'), Index(['A', 'B', 'C', 'D'], dtype='object')]

# DataFrame from Dictionary

>>> data = {'a': 10, 'b': 20, 'c': 30, 'd': 40}
>>> index = [1, 2, 3, 4]
>>> pd.DataFrame(data, index) a b c d
1 10 20 30 40
2 10 20 30 40
4 10 20 30 40

# DataFrame from Dictionary of Lists

>>> data = {'a': [10, 15, 20, 25], 'b': [30, 35, 40, 45], 'c': [50, 55, 60, 65],
'd': [70, 75, 80, 85]}

>>> index = [1, 2, 3, 4]

>>> pd.DataFrame(data, index) a b c d


1 10 30 50 70
2 15 35 55 75
3 20 40 60 80
4 25 45 65 85

# DataFrame from List of Dictionaries


>>> data = [{'x':2, 'y':10}, {'x':4, 'y':20}, {'x':6, 'y':30}, {'x':8, 'y':40}]
>>> d = pd.DataFrame(data, index = [‘a’, ’b’, ’c’, ’d’])

>>> d
xy
a 2 10
b 4 20
c 6 30
d 8 40
# Calling by row index and columns

>>> d['x']
a2
b4
c6
d8
Name: x, dtype: int64

>>> d['x'][‘b’] 4
# DataFrame from Dictionary of Series
>>> index = ['a', 'b', 'c', 'd'] >>> s1 = pd.Series([5, 8, 9, 11], index)

>>> s2 = pd.Series([7, 6, 4, 3], index)


# Dictionary of two Series
>>> d = {'A':s1, 'B':s2}
>>> D = pd.DataFrame(d)
>>> D
AB
a57
b86
c94
d 11 3

# Add a new column to DataFrame

>>> D['C'] = pd.Series([10, 20, 30, 40], index)


>>> D
ABC
a 5 7 10
b 8 6 20
c 9 4 30
d 11 3 40

# We can add anotherDataFrame also. >>> D['C'] = pd.DataFrame({'C':


pd.Series([10, 20, 30, 40], index)}) # Location

# Locate through index name >>> D.loc['b']


A8
B6
C 20

# Locate through index position >>> D.iloc[1]


A8
B6
C 20
Name: b, dtype: int64

# Slicing
>>> D[1:3]

A B C b 8 6 20
c 9 4 30
>>> D[1:3]['A']
b8
c9
Name: A, dtype: int64

# Append
>>> D1 = pd.DataFrame([[-1, 0, 2]], index = ['e'], columns = ['A', 'B',
'C']) >>> D1
ABC
e -1 0 -2
# Append DataFrame
>>> D.append(D1)

ABC
a 5 7 10
b 8 6 20
c 9 4 30
d 11 3 40
e -1 0 -2
# Delete

# Deleted the indexed row >>> D.drop('a’)


ABC
b 8 6 20
c 9 4 30
d 11 3 40
e -1 0 -2

11.2.1 Reading Data as DataFrame

Consider that we have written data in a spread sheet or we want to extract


data from a downloaded Excel sheet. We can read the file through a CSV
reader, read_csv() in Pandas. The output is a DataFrame. Let us check the
directory of Pandas.

>>> import pandas as pd


>>> dir(pd)
['Categorical', 'CategoricalDtype', 'CategoricalIndex', 'DataFrame',
'DateOffset', 'DatetimeIndex', 'read_html',
'read_msgpack', 'read_pickle', 'read_sql',
'read_sql_table', 'read_table',
'DatetimeTZDtype', 'ExcelFile', 'ExcelWriter', ……
…..
, 'pivot_table', 'plotting', 'qcut', 'read_clipboard', 'read_csv', 'read_excel',
'read_feather', 'read_fwf', 'read_gbq', 'read_hdf',

'read_json', 'read_parquet', 'read_sas', 'read_sql_query', 'read_stata',


'reset_option', 'set_eng_float_format', 'set_option', 'show_versions', 'test',
'testing', 'timedelta_range',

'to_msgpack',
'to_pickle',

'to_datetime', 'to_numeric', 'to_timedelta', 'tseries', 'unique', 'util',


'value_counts', 'wide_to_long']

We can seek help of any file reader by typing on interpreter. Type example.
Let us

help(read_csv), for now read the following downloaded Microsoft Excel


sheet:

# Reading data through Pandas function


>>> df =
pd.read_csv('C:/Users/Abhijit/Down loads/covid.csv') >>> type(df)
<class
'pandas.core.frame.DataFrame'>

>>> df.head() # Showing first 5 rows Province/State Country/Region Lat ...


3/21/20 3/22/20
0 NaN Thailand 15.0000 ... 411 599 1 NaN Japan 36.0000 ... 1007 1086 2
NaN Singapore 1.2833 ... 432 455 3 NaN Nepal 28.1667 ... 1 2 2.0
4 NaN Malaysia 2.5000 ... 1183 1306 [5 rows x 66 columns]

Note: Before the file to be read byread_csv(), the Excel sheet should be saved
in the csv format. [If the file is not in the same directory, we need to write the
file name along with the path name: ~/Downloads/covid.csv.]

The data-file that is read by the csv reader, is a DataFrame. We can check the
head and tail [by default first and last 5 lines] of the DataFrame. We may
check any number of lines from the top or end throughdf.head(10),
df.tail(50). # Convert DataFrame to NumPy array

>>> L = df.to_numpy()
>>> L
array([[nan, 'Thailand', 15.0, ..., 411L, 599L, 599.0],[nan, 'Japan', 36.0, ...,
1007L, 1086L, 1086.0],[nan, 'Singapore', 1.2833, ..., 432L, 455L, 455.0],

...,
[nan, 'Republic of the Congo', 1.44, ..., 0L, 0L, 0.0],[nan, 'The Bahamas',
24.25, ..., 0L, 0L, 0.0],[nan, 'The Gambia', 13.4667, ..., 0L, 0L, 0.0]],
dtype=object)

# Reading a plain text/ data file Following Data File (a plain text file:
test.dat):

City Petrol Diesel Tea


Kolkata 80 70 6
Mumbai 85 75 10
Delhi 82 74 8
Begaluru 81 72 11
Pune 84 73 12
Siliguri 76 69 5

>>> df = pd.read_csv('test.dat') >>> df

City Petrol Diesel Tea


0 Kolkata 80 70 6
1 Mumbai 85 75 10
2 Delhi 82 74 8
3 Begaluru 81 72 11
4 Pune 84 73 12
5 Siliguri 76 69 5
>>> type(df)
<class
'pandas.core.frame.DataFrame'>

Note: Once the data file is read asDataFrame, one can convert this into a
NumPy array or a Python list. [FromDataFrame to List:
df.values.tolist()]

In the hierarchical form of structured data, Panel is a box containing the 2D


DataFrame. However, in Python 3, Panel is discontinued. So, we will not
make any discussion on this.

Elementary Python Codes


“Code is more often read than written.” – Guido van Rossum
In this chapter, we write functional Python scripts some elementary

following various algorithms. We adhere to the Python syntax and methods


that we learnt so far. Many of the explicit blocks of codes can be simply
replaced by built-in functions or by functions imported from various modules
and packages. Let us try to write simple and minimal codes by Python.

[The art of writing a code is up to an individual. A computer code can be


written in various ways even if the algorithm, that is followed, is the same.]

# 1 Prime Numbers

Prime numbers are those integers which are not divisible by any number
except 1 and the same number. For example, 3, 5, 7, 11, 13…. are prime
numbers.
By Primitive Algorithm

Algorithm:

1. Check if the numbern is divisible by any integer between2 to n-1. If so, the
number is not prime. Break away.

2. Run a loop between 2 to n-1 to test the step 1.


3. End of loop (no break away) means, the number is prime.

# Python Script: Prime Number def prime(n):


import sys

for i in range(2, n): if n%i == 0:


return 'Not Prime’ sys.exit()

return ‘Prime’

Note: To exist from the loop, we used sys.exit() fromsys module. We can
also use the built-in key word break (operates under a loop) instead of
importing sys module.

def prime(n):
for i in range(2, n): if n%i == 0:
return 'Not Prime' break
else:
return 'Prime'
Note: In this case, else is used with respect to the for loop.
With Faster Algorithm

If we observe the decomposition of a number, there is a scope for


improvement in our algorithm. In the following demonstration, it can be
noticed that the decomposition scheme repeats after the square root of the
number. Therefore, the loop should run from 2 to √�� and not from2 ton-1.

36 12

2 × 18 2 × 6
3 × 12 3 × 4
4 × 9 √12 ×√12
6 × 6 ←√36 4 × 3
9×46×2
12 × 3
18 × 2

# Python Script:
# Prime Number (faster algorithm) from math import sqrt

def prime_fast(n):
max = int(sqrt(n))
for i in range(2, max+1):

if n%i == 0: return False else: # else w.r.t for-loop


return True

Exercise: Find out how many prime numbers are there within a given range
[100, 100000].
Count Prime Numbers from a List

In the following, a Python Script is written to compute the number of primes


within a given range and the total time taken. To count time, we import time.

# Python Script:
# Number of primes in a range

“““
To count number of primes up to some number, we should count only odd
numbers, for a faster computation, as even numbers are not prime. We start
from 3, as 1 is not a prime. We print count + 1 as 2 is even but a prime. The
computer time is also checked.
”””
import time
t1 = time.time() # Clock starts L = range(3, 100000, 2)
count = sum([prime_fast(n) for n in

L]) t2 = time.time() # Clock ends print(t2 – t1)

# Total execution time


print(‘No.of primes = ‘, count + 1)

Exercise: Compare the difference in speeds between two algorithms.


Compute total times taken by prime() and byprime_fast().

# 2 Strong Numbers
A strong number is some integer where the sum of factorials of its digits
equals to the number itself. For example: 145 = 1! + 4! + 5! We find out
strong numbers between 1 and some large number.

Algorithm:

1. Take input number as a string.


2. Evaluate a string element, take factorial.
3. Repeat step 2 in list comprehension.
4. Compute sum of the list.
5. Compare the sum with the original number.

# Python Script: Strong Number from math import factorial


N = input(‘Enter number’)
x = sum([factorial(eval(i)) for i

in N]) if x == N: print(‘Strong’)
Exercise: Discover how many strong numbers are there up to 1000000.
# 3 Max, Min

Take a list of numbers as input [or accept numbers sequentially through


input() function]. Check every number if that is greater (or smaller) than the
previously assumed max (or min) number.

Algorithm:

1. Input: list of numbers


2. Assign first number as max (or min)
3. Go to the next number. If this is greater (or smaller) than the previous
number, call this maximum (minimum).
4. Repeat the step 3 in a loop for all the numbers in the list.

# Python Script: # To Find Max and Min L = [2, 6, 9, 0, -1] max, min =
L[0], L[0]

for i in L[1:]:
if i >= max: max = i if i <= min: min = i
print('Max = ', max, 'Min = ', min)

Note: L[0] = 2, is the 0th element of the list. L[1:] is the sliced list of
elements from index 1 to the end.

Written as Python function:

def maxi(L):
mx = L[0]
for i in L[1:]:

if i >= mx: mx = i return mx

Let us compare the performance of our defined function maxi() function


max(). with respect to the built-in We find out maximum from a

list (or tuple). We note the execution time. For a small list, the difference may
not be noticeable. But for a very large list of numbers, the difference (in
execution time by the two ways) will be appreciable.

# Python Function: To compute time def test_time(L):


import time
# Initial time t1 = time.time()
maximum = maxi(L)

# Final time
t2 = time.time()
return maximum, t2 - t1

# Create a big list of random numbers and test


>>> import numpy as np
>>> L = random.uniform(1, 10, 100000000) >>> test_time(L)

Exercise:
Use the test_time() function to compute difference in times taken bymaxi()
function (user defined) and max() function (built-in).

# Python Script: Find maximum [With data entered one-by-one from


outside.]

max = eval(input('Enter a number \n'))


while True:
num = eval(input('Enter another \n')) if num >= max: max = num print
('Max = ', max)

Note:
The‘while True:’ is an infinite loop. It can

continue until we want or until the condition is satisfied. No logical condition


is needed in this. The input is accepted as long as we enter data. In case we
want to break away, we may just press enter or press ‘contrl c’ etc. (on
interpreter). For example, we can replace the infinite loop on left by the
‘while True’ loop on right:

x=1
while x < 5: print(‘Continue’) while True:
print(‘Continue’)

# 4 Sorting a List
Bubble Sort

Bubble sort is the simplest sorting algorithm that works by repeatedly


swapping the neighboring elements if they are in wrong order. Let us look at
the following demonstrations for the list: [6, 2, 9, 0, 1]

First Pass
[6, 2, 9, 0, 1] ⟶ [2, 6, 9, 0, 1] Swap
[2, 6, 9, 0, 1] ⟶ [2, 6, 9, 0, 1] No swap
[2, 6, 9, 0, 1] ⟶ [2, 6, 0, 9, 1]

Swap
[2, 6, 0, 9, 1] ⟶ [2, 6, 0, 1, 9]
Swap
Second Pass

[2, 6, 0, 1, 9] ⟶ [2, 6, 0, 1, 9]
No swap
[2, 6, 0, 1, 9] ⟶ [2, 0, 6, 1, 9]

Swap
[2, 0, 6, 1, 9] ⟶ [2, 0, 1, 6, 9]
Swap
Third Pass

[2, 0, 1, 6, 9] ⟶ [0, 2, 1, 6, 9]
Swap

[0, 2, 1, 6, 9] ⟶ [0, 1, 2, 6, 9] Swap


Fourth Pass

[0, 1, 2, 6, 9] ⟶ [0, 1, 2, 6, 9] No Swap


# Bubble sort (ascending order)
L = [6, 2, 9, 0, 1]

def bubble(L):
n = len(L)
for i in range(n):

for j in range(n-i-1):`
if L[j] > L[j+1]:
# Swap
L[j], L[j+1] =

L[j+1], L[j] return L


print('Sorted list = ', bubble(L))
Output:
Sorted list = [0, 1, 2, 6, 9]
Note: To sort in descending order, we need to write:L[j] < L[j+1] inside the
jloop.
Bubble sort with flag:

The bubble sort algorithm can be implemented with a flag that will keep track
of the swaps. If no more swap is required for a subsequent pass, it can break
away. This way we can save computation time. This is only refinement.
def bubble_flag(L):
n = len(L)
for i in range(n):

flag = 0
for j in range(n-i
1): if L[j] >

L[j+1]:
flag = 1
L[j], L[j+1]

= L[j+1], L[j] if flag == 0: break


return L
Insertion Sort
Algorithm: (For ascending order)

1. Start from the 2nd element. If it is smaller than the 1st, then swap.
2. Go to the 3rd element. First, compare it with the 2nd, if smaller, then
swap. Next, compare with the 1st and do the same.
3. Repeat Step 2 for all subsequent elements. [Each element must be checked
with all preceding elements.]

# Python Script: Insertion Sort


L = [10, -2, 0, 1, 100, 15, 9, -12]

def sort_insert(L):
n = len(L)
for i in range(1, n):

key = L[i]
j=i-1
while j >= 0 and L[j] >

key: L[j+1] = L[j]


j = j -1 L[j+1] = key return(L)
print(sort_insert(L)) # Sort by built-in function
>>> L = [10, -2, 0, 1, 100, 15, 9, -12]
# Ascending by default
>>> sorted(L)
[-12, -2, 0, 1, 9, 10, 15,
100]

# Descending
>>> sorted(L, reverse =
True) [100, 15, 10, 9, 1, 0, -2,

12]
>>> L
[10, -2, 0, 1, 100, 15, 9,

12] [Original List unchanged.]


# Sort by Method on List # Ascending
>>> L.sort()

# Descending
>>> L.sort(reverse = True) >>> L
[-12, -2, 0, 1, 9, 10, 15,

100] [Original List changed.]

We can test various sorting functions by comparing the execution times taken
to complete a job. Let us create a large array of numbers by random number
generator inNumPy (This external package will be introduced in the next
chapter.). Also, let us import the module time to keep track of time.

# For time Calculation

import numpy as np
import time
L = np.random.uniform(-1, 1,

10000) def test(L):


t1 = time.time() bubble(L)
t2 = time.time() return t2 - t1
Note: For 10000 random numbers, the user defined bubble sort
functionbubble() took around 22 sec and that by insertion sort took around 9
sec. But sorted() and list.sort() took only a fraction of a sec (around 0.01 sec
and less) on the same machine.

[Evidently, the built-in function and list method for sorting must be based on
some better algorithm than bubble and insertion sort algorithms. There are
many efficient sorting algorithms like Quick sort, Bucket sort, Tim sort and
others which are fascinating but those are beyond the scope of this book.]

Exercise: Create a list of 100,000 random numbers between 0.5 and 2.5 and
sort them in ascending (and in descending) order. Use the defined functions
by Bubble sort, Insertion sort and the built-in methods. Note the execution
times taken by various methods and conclude.

# 5 Roots of Quadratic Equation

Solution to the quadratic equation, ����2+ ���� + �� = 0 is given


by the formula: ��1, ��2=(−�� ± √��2− 4����)/2��. So,
depending on the term inside the square root, the solutions are either real and
distinct or equal real roots or a pair of complex conjugates.

# Python Script:
# Roots of Quadratic Equation from math import sqrt
a, b, c = eval(input('Enter a, b,

c \n')) X = b**2 - 4*a*c


p = sqrt(abs(X))
q = 2*a

if X > 0:
print('Distinct Real roots = ', (-b + p)/q, (-b - p)/q) if X == 0:
print('Equal Real roots = ', b/q, -b/q) if arg < 0:
print 'Complex roots = ', complex(-b, p)/q, complex(b, - p)/q

Note: The built-in function complex()makes a complex number. [We shall


learn how to find out the roots of polynomials using NumPy polynomial
module in the chapter on NumPy.]
# 6 Mean, Variance, Std Deviation Mean =
1∑��, Variance =1∑��2− (1∑��)2
�� �� ��
# Python Script: Mean and Variance import math
# List of numbers
x = [2, 3, 0, 1, -2, 5]
# By List Comprehension y = [i**2 for i in x]
n = len(x)

mean = sum(x)/n
var = sum(y)/n - mean**2 stdev = math.sqrt(var)

print ('Mean, Variance, Standard dev = ', mean, var, stdev)


# 7 Correlation Coefficient
Pearson’s Correlation coefficient formula between two sets of variables
(��, ��)
:
�� =
������
��������

Where the variances, ����2=1∑(����− ��̅)2, ����2=��


1∑(�� 2 1
��− ��̅) and the covariance, ������= ∑(����−�� ��
��̅) (����− ��̅). With these, the correlation formula is

∑(����− ��̅) (����− ��̅)�� = √∑(����− ��̅)2.√∑(����− ��̅)2


# Correlation Coefficient
from math import sqrt
x = [2, 4, 5, 6, 8] y = [5, 9, 11, 10, 12]

n = len(x)
xav = sum(x)/n yav = sum(y)/n

sigx = sqrt(sum([(i-xav)**2 for i in x]))


sigy = sqrt(sum([(i-yav)**2 for i in y]))
covxy = sum([(i-xav)*(j-yav) for i,j in zip(x,y)])
corr = covxy/(sigx*sigy)
print('Corr Coefficient = ', corr)

Output
Corr Coefficient = 0.910366477463

Note: The zip(x,y) function makes a list of (x, y) tuples taking respective
elements from the corresponding lists x and y.

# Example: zip()

>>> x = [1, 2, 3]
>>> y = [4, 5, 6]
>>> zip(x, y)
[(1, 4), (2, 5), (3, 6)]

# Corr. Coefficient with NumPy import numpy as np

x = np.array([2, 4, 5, 6, 8]) y = np.array([5, 9, 11, 10,

12]) r = np.corrcoef(x, y)
print (‘Corr Coeff = ‘, r)

Output
Corr Coeff =
[[1. 0.91036648] [0.91036648 1. ]]

Note: In the last output, the 4 elements (in matrix form), are to be read
as:������, ������, ������, ������.
# 8 Infinite Series

Exponential Series
Taylor series expansion:
exp(��)= 1 + �� +

��2 ��3
2!+3!+⋯.∞
Algorithm:
1. Input:��, tolerance value (accuracy level)
2. Initial values: sum = 0, term = 1.0
3. Loop starts.
4. Inside loop: update sum, update term

new = old
×
��
��
5. Loop continues until condition is fulfilled.
# Python Script: Exponential Series import math
x, tol = eval(input('Enter x and tolerance \n'))

sum, term = 0.0, 1.0


n=1
while abs(term) > tol:

sum += term
term = term*x/n
n += 1
print('Calculated Value, Actual Value') print(sum, math.exp(x))
Input/Output

Enter x and tolerance


2, 0.001
Calculated Value, Actual Value 7.38871252205 7.38905609893

Cosine Series
Taylor series:
cos(��)= 1 −
��2 ��4 ��6
2!+4!−6!+⋯∞
Algorithm:

1. Input:��, tolerance value


2. Initialize 1st term, sum
3. Loop starts, continues till abs value of the term remains more than
tolerance value.
4. Compute the sum of terms
5. Upgrade the term
6. Upgrade the term number

# Python Script: Cosine Series import math

x, tol = input('Angle in deg, tolerance: \n')


# Converted to radian
x = x*math.pi/180

sum, term = 0.0, 1.0


n=1
while abs(term) > tol: sum += term
# term update
term = term*(-x**2/(2*n*(2*n1))) n += 1
print ('Calculated Value, Actual Value') print (sum, math.cos(x))
Input/Output:
Angle in deg, tolerance:

45, 0.001
Calculated Value, Actual Value
0.70742920671 0.707106781187

Sum by List Comprehension


��
=∑

��
��
Exponential series: ����=0 ��!
from math import factorial as f ex = lambda x, n: sum([x**i/f(i) for i in
range(n)])

>>> ex(1, 10)


2.7182815255731922 >>> ex(1, 20)
2.7182818284590455

With Tolerance Value


# Python Script: with tolerance value
tol = 0.0001
x, n = 1, 2
while abs(ex(x,n)-ex(x,n-1))> tol: n = n + 1
print(n, ex(x, n))

Output:
3 2.5

4 2.6666666666666665
5 2.708333333333333
6 2.7166666666666663
7 2.7180555555555554
8 2.7182539682539684
9 2.71827876984127

#
Cosine series:
cos �� =∑
����2�� ��=0 (2��)!
∞ (−1)

# Python Script: Cosine Series (with tolerance)


from math import factorial, pi C = lambda x, n: sum([(

1)**i*x**(2*i)/factorial(2*i) for i in range(n)]) theta = 60


x = theta*pi/180
n, tol = 2, 0.00001
while abs(C(x,n) - C(x,n-1)) > tol:

n=n+1
print(n, C(x, n))
Output:

3 0.501796201500181
4 0.4999645653289127
5 0.500000433432915
6 0.4999999963909432

Exercises:
Write python codes to compute the following series sums. Set tolerance value
= 0.0001.
1. Sine Series:
sin (x) = �� −��
357
3!+��5!−��7!+ ⋯ ∞2.
Logarithmic Series:
(1 + ��)= �� −��
23
log�� 2+��3− ⋯ ∞
Verify this:log��2 = 1 −1+1−1+ ⋯ ∞2 3 4 3. Walli’s formula:
��= lim (2.2.4.4.6…2��−2. 2��. 2��). 2 ��→∞ 1 3 3 5 5 2��−1 2��−1 2��+1
Verify this numerically.
4.
Verify the Fourier
series:
�� = 2 (
sin�� 1 − sin
2�� sin3��
+3 − ⋯ ) 2
# 9 Digits of a Number

Any number taken through input() is a string in Python 3. A string is iterable.


So, we can read the characters (i.e., the digits) under a for-loop.

# Input as string, read the digits

>>> n = input('Enter Number \n') Enter Number


679135
>>> n
'679135'
>>> for i in n:

print(i, end = " ")

6 7 9 1 3 5 # Sum of digits of a number (With List Comprehension)

>>> sum([eval(i) for i in n]) 31


# 10 Check Leap Year
Earth takes 365 days, 5 hours, 48 minutes, and 45 seconds – to revolve once
around the Sun. But our Gregorian calendar considers 365 days in a year. So,
a leap day is regularly added (to the shortest month February) in every 4
years to bring it in sync with the Solar year. After 100 years our calendar
does have a round off by around 24 days.

In the Gregorian calendar three criteria must be considered to identify leap


years:

• The year can be evenly divided by 4;


• If the year can be evenly divided by 100, it is NOT a leap year, unless;
• The year is also evenly divisible by 400. Then it is a leap year.

This means that in the Gregorian calendar, the years 2000 and 2400 are leap
years, while 1800, 1900, 2100, 2200, 2300 and 2500 are not leap years.

Algorithm:

1. Input: the number


2. Check if it is not divisible by 100 but divisible by 4
3. Check if it is divisible by 400
4. For steps 2 and 3, the Number is Leap year, otherwise it is not.

# Leap year Calculation


year = eval(input('Enter Year \n'))

if year%100 != 0 and year%4 == 0: print('Leap Year')


elif year%400 == 0:
print('Leap Year')
else:
print('Not a Leap Year')

# 11 Collatz Sequence

It is a simple but fascinating number sequence that follows simple rules.


“The Collatz conjecture is a conjecture in mathematics named after Lothar
Collatz. It concerns a sequence defined as follows: start with any positive
integer n. Then each term is obtained from the previous term as follows: if
the previous term is even, the next term is one half the previous term.
Otherwise, the next term is 3 times the previous term plus 1. The conjecture
is that no matter what value of n, the sequence will always reach 1.” –
Wikipedia

Algorithm:

1. Enter the number.


2. If it is even, divide by 2.
3. If it is odd, multiply by 3 and add 1.
4. Follow the steps 2 and 3 until the number turns to 1.

# Python Script: Collatz Sequence n = eval(input('Enter Number \n'))


while n > 1:

if n%2 == 0: # n is even n = n/2


else: # n is odd n = n*3 + 1
print(int(n), end = “ ”)

Input/Output

Enter Number
12
6 3 10 5 16 8 4 2 1

# 12 GCD and LCM

This is to find the Greatest Common Divisor (GCD) and Lowest Common
Multiplier (LCM) of two positive integers by Euclid method. First, we divide
the bigger number by the smaller number and consider the residue. If the
residue is zero, then the smaller number is the GCD. If this is not the case, we
take the nonzero residue as the smaller number and previous smaller number
to be the bigger number. This is continued until we get a zero residue. Then
at the last step, the divisor is called the GCD. LCM is just obtained by
dividing the product of two original numbers by GCD.

Algorithm:
1. Input two numbers m and n.
2. Find residue by dividing “m” by “n” [m > n]
3. If the residue (in step 2) is zero, GCD= n
4. If the residue is not zero, redefine:m = n, n = residue
5. Repeat the steps 2 to 4.
6. Calculate LCM = m*n/GCD

# Python Script: GCD of two Numbers


# Give m > n
m, n = eval(input('Enter m, n \n'))

def gcd(m, n):


nprod = m*n
r = m%n # residue while r > 0:

m, n = n, r
r = m%n
return n print('GCD = ', gcd(n), 'LCM = ', nprod/gcd(n))
Note: For any two numbers �� and ��, we can always swap them to
have m > n.

>>> m, n = 4, 18
>>> if m < n: m, n = n, m >>> m, n
(18, 4)

# 13 Pythagorean Triplets

Euclid’s formula for generating Pythagorean triplets begins with a pair of


positive integers �� and ��, where they are co-prime. What is coprime?
Two integers �� and �� are said to be coprime (or relative prime or
mutual prime) when the greatest common divisor between them is 1. For
example, 14 and 15 are coprime but 14 and 21 are not coprime as they both
are divisible by 7. Given two co-primes�� and ��, the formulas to find
Pythagorean triples (��, ��, ��), so that��2+ ��2= ��2: �� =
����− ����, �� = ������, �� = ����− ����

Algorithm:
1. Input: a positive integer��
2. Take an integer�� in the range [1, (�� − 1)]
3. Find GCD between �� and ��
4. Test if GCD = 1, calculate ��, ��, �� from the formulas.

# Pythagorean Triplets by Euclid's formula (m, n -> Coprime)


n = eval(input('Give a number \n'))

for m in range(1, n):


if gcd(n, m) == 1: x = n*n - m*m
y = 2*n*m
z = n*n + m*m
print (x, y, z)

Output:

Give a number
3
Pythagorean triplets 8 6 10
5 12 13

# 14 Number Conversion
Decimal Integer to Binary
Base 10 system ⟶ Base 2 2)156 0system. 2)78 0To convert, we divide the 2)39 1decimal number by 2
and 2)19 1continue downwards, divide 2)9 1each new quotient by 2 and
2)4 0

write the remainders to the 2)2 0right of each dividend. It is 1done


when the quotient is 1.
For example, for the decimal number 15610 is converted into a binary number
100111002 . Algorithm: (For Integer)

1. Input number as a string


2. Divide the number by 2 (modulo 2), store the remainder.
3. Add remainder to string (concatenation)
4. Take the quotient (integer) for the next step.
5. Repeat steps 3 - 5 while the quotient is greater than 0.
6. Invert the string to get the resulted binary number

#Decimal Integer to Binary


n = eval(input('Enter the Decimal number \n'))
def bin_int(n):
b = str() # Empty string while n > 0:
res = n%2

# Added to the string b += str(res)


n = n//2 # floor division
return b[::-1] # reversed
print(bin_int(n))
Decimal Fraction to Binary

For the conversion of fractional part, we need to multiply by 2 and collect


integer part (either 0 or 1) of the product. Separate out the fractional part and
do this repeatedly.

# Decimal Fraction to Binary


from math import modf
n = eval(input('Enter fract \n'))

def bin_frac(n):
b = str()
i=1
while n > 0:

n = n*2
frac, intg = modf(n) b += str(int(intg)) n = frac
i=i+1

# Tried up to 6 digits if i > 6: break


return b
Note: The modf() function in math module splits the decimal and fractional
part of a number.

>>> from math import modf >>> modf(23.57)


(0.5700000000000003, 23.0)

Exercise: Convert the decimal number: 23.57 into a binary fraction.


[ Hint: Split the number into integer and fraction part with modf() function.
Then use the two functions bin_int() and bin_frac() as defined above. At the
end, represent the entire binary number by joining the two output strings:
bin_int(23) + ‘.’ + bin_frac(57)]

Binary Integer to Decimal

In this case we must take each digit in respective positions and sum the terms
with 2 to the power of that position. For example, �������� →
�� × ����+ �� × ����+ �� × ����+ �� × ����.

# Python Script: Binary to Decimal n = eval(input('Give the Binary


number \n'))

s, i = 0, 0
while n > 0:
res = n%10
n = n//10

# Power of 2 for Binary s += (2**i)*res


i += 1

print(‘Decimal Number:', s)
Input/Output:

Give the Binary number 101


Decimal Number: 5
Note: For Decimal to Octal conversion, replace 2 by 8 in the above code.

# Conversion by built-in function >>> bin(12) # Decimal to Binary

'0b1100'
>>> bin(-123)
'-0b1111011'
>>> 0b1100 # Binary to Decimal 12

Note: For the above expressions, the prefix ‘b’ in the binary string stands for
byte string.
Exercises:

1. Write a Python Program for converting a Decimal number (fraction) to


Octal. [Hint: Follow the program for converting Decimal to Binary (as
written above), and just replace 2’s by 8’s in places. Take modulo 8, and then
divide by 8. Try this.] Verify:(110.16)10= (156.121727. .)8

2. Write a program to convert an Octal number to Decimal. [Hint: Only


change you must do is to replace 2 by 8 in two places.]

# 15 Fibonacci Numbers

Fibonacci series is a series of numbers where each number is the sum of two
previous numbers:0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89….
The rule: ����+2= ����+ ����+1

Algorithm:

1. Initial list with 0 and 1.


2. Add two last elements by list indexing and append to list.
3. Repeat the step 2 in a loop.

# Python Script: Fibonacci f = [0, 1]


for i in range(10):

# Sum of last two terms appended f.append(f[-1] + f[-2]) print(f)


print('Golden Ratio = ', f[-1]/f[

2])
Output:

[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]


Golden Ratio = 1.61818181818

# 16 Binary Search

Binary search algorithm is used to find any number in a sorted list. The given
list must be sorted first (in descending or ascending order). If the wanted
number is found in the sorted list, then its index is returned. This is to find the
position of the number in the list. This algorithm works on‘divide and
conquer’ principle!

Algorithm:

1. Input: Sorted List


2. Find the mid position of the sorted elements
3. If the element corresponding to that position matches with the given
number, we found where the element is. Break away from the loop.
4. If not, check the element whether it belongs to below the mid-point or
above.
5. Take appropriate half and repeat steps 3 - 4.
6. The search continues until we arrive at the single position and check.

# Python Script:
# Binary Search by Algorithm L = [1, 0, 10, -9, 6, 100, -23, 7] SL =
sorted(L)
x = eval(input('Number to search:

\n')) start = 0
end = len(SL)-1

while start <= end:


mid = int((start + end)/2) if x == SL[mid]:

print('Position in sorted List:', mid)


break
elif x < SL[mid]:
end = mid - 1
else:
start = mid + 1
print('start = ', start, 'end = ', end, 'mid = ', mid)

if x != SL[mid]: print ('Not in list')

Output:
[-23, -9, 0, 1, 6, 7, 10, 100]
Number to search:
6
start = 4 end = 7 mid = 3
start = 4 end = 4 mid = 5
Position in sorted List: 4

Note: The above output shows the steps how the position of a given number
is searched through the bracketing of starting and end points. Note that the
position of an element (here the number 6) is given in the sorted list (and not
in the original list).

Searching by List Method

The position of an element in a list can be found out by the


method,list.index(). This returns the position index of an element.

>>> L = [1, 0, 10, -9, 6, 100, -23, 7]


>>> L.index(6)
4
>>> x = [3, 0, 5, 'a', 'b']
>>> x.index('a')
3

# Also, to test if an element is in the list


>>> L.index(50)
Traceback (most recent call last): File "<pyshell#4>", line 1, in

<module>
nlist.index(50)
ValueError: 50 is not in list

# 17 Matrix Operations

Two dimensional lists (lists inside a list) and 2D NumPy arrays can be treated
as Matrix. [NumPy arrays will be introduced in detail in Chapter 7 on
NumPy.]

Addition of Two Matrices


For addition of two matrices, the rows, and columns of them must be equal.
The (��, ��)-th element of a matrix is added with the same(��, ��)-
th element of another to obtain the(��, ��)-th element of the new
matrix:������= ������+ ������.

Algorithm:

1. Input: two matrices in the form of lists.


2. Loops for rows and columns (��, ��)
3. Sum the corresponding elements of two lists:��[��][��] =
��[��][��] + ��[��][��]

# Matrix addition (with Core Python)


A = [[1, 2, 3], [4, 5, 6]] B = [[5, 6, 4], [3, 4, 2]]
# Initialization of matrix C C = [[0, 0, 0], [0, 0, 0]]
row = len(A)
col = len(A[0])
for i in range(row):
for j in range(col):
C[i][j] = A[i][j] + B[i][j]
print(‘Matrix C’)
# To print in Matrix form for r in C:
print(r)

Output:
Matrix C [6, 8, 7] [7, 9, 8]

Note: Every 2D list can be arranged to look like a mathematical Matrix. For
example,
A = [[1, 2, 3],

[4, 5, 6]]

The list A has two elements where each element is a list itself. The size of list
A (number of elements) is equal to rows. The size of a row is equal to
columns.

The nested 2D lists A, B, C are treated as matrices and they have same shape.
# Number of elements = rows
>>> len(A)

# First element
>>> A[0] [1, 2, 3]

# Number of elements = columns >>> len(A[0])


3

To create the resulting matrix C, we need to create or initialize the matrix.


This is done by creating a 2D list C with all zero entries.

# Python Script: Matrix addition (by List comprehension)

A = [[1, 2, 3], [4, 5, 6]] B = [[5, 6, 4], [3, 4, 2]] row, col = len(A), len(A[0])

C = [[A[i][j] + B[i][j] for j in range(col)] for i in range(row)]


print(‘Matrix C’) for r in C:
print(r)
# Input from outside

# m = rows, n = columns m, n = 2, 3
print ('Matrix elements for

A: Enter one at a time') A = [[eval(input()) for j in range(n)] for i in


range(m)]
Product of Two Matrices
��
������=(����)����=∑ ������������, for the Product
of
��=1

two matrices, the number of columns of the first matrix must be equal to the
number of rows of the second matrix.

Algorithm:
1. Input: Two matrices as nested lists
2. Nested loops with indices:i, j, k (say,)
3. Inside the innermost loop (k say,) the (��,
��) -th element of 1st matrix is multiplied with the (��, ��)-th element
of 2nd matrix, and then added.

# Python Script: Matrix Product


# 2×3 matrix
A = [[1, 2, 3], [4, 5, 6]]
# 3×2 matrix
B = [[1, 2], [3, 4], [5, 6]]
C = [[0, 0], [0, 0]] # 2×2 matrix
for i in range(len(A)):
for j in range(len(B[0])): for k in range(len(B)): C[i][j] +=
A[i][k]*B[k][j]
print ('Matrix C') for row in C:
print (row)

Output: Matrix C [22, 28] [49, 64]

# Matrix Product by List Comprehension


A = [[1, 2, 3], [4, 5, 6]] B = [[1, 2], [3, 4], [5, 6]] m, p, n = len(A), len(B),
len(B[0])
C = [[sum([A[i][k]*B[k][j] for k in range(p)]) for j in range(n)] for i in
range(m)] for row in C:
print (row)
# Matrix product using built-in function zip()
A = [[1, 2, 3], [4, 5, 6]] B = [[1, 2], [3, 4], [5, 6]]
C = [[sum(i*j for i,j in zip(A_row, B_col)) for B_col in zip(*B)] for
A_row in A] for row in C:
print (row)
How doeszip() work?

>>> x = [1, 2, 3] >>> y = [4, 5, 6] >>> zip(x, y)

<zip object at
0x0000028B1935AF40>
[New iterable object from two lists]

# Convert to list
>>> list(zip(x, y))
[(1, 4), (2, 5), (3, 6)]

>>> A = [[1, 2, 3], [4, 5, 6]]

# From the same list


>>> list(zip(*A)) [(1, 4), (2, 5), (3, 6)]
>>> B = [[1, 2], [3, 4], [5,

6]] >>> list(zip(*B))


[(1, 3, 5), (2, 4, 6)]

Transpose of a Matrix

By transpose of a matrix A, the rows become the columns and the columns
become rows. If B is the transposed matrix: B =����, the elements of the
two matrices will be related as������= ������
Algorithm:

1. Input: Matrix
2. Find the number of rows and columns
3. Nested Loops for i and j: i for rows (outside), j for columns
4. Inside the inner loop:������= ������

# Transpose of a Matrix

A = [[1, 2, 3], [4, 5, 6]] row = len(A)


col = len(A[0])

for i in range(row):
for j in range(col): B[j][i] = A[i][j]
for r in B: print(r)
# Transpose by List comprehension
A = [[1, 2, 3], [4, 5, 6]] row = len(A)
col = len(A[0]) B = [[A[i][j] for i in range(row)] for j in range(col)]
print('Transposed Matrix') for r in B:
print(r)

Output:
Transposed Matrix

[1, 4]
[2, 5]
[3, 6]

Matrix Operations with NumPy

Matrix operations can be greatly simplified and made faster with the help
ofarrays in NumPy. NumPy is an external package developed for Python
which consists of various useful modules, functions and other objects suited
to do efficient numerical computations. We shall introduce NumPy in detail
in the next chapter.
In this chapter, we briefly demonstrate how we can do matrix operations
witharray() function in NumPy package. Like nested lists, NumPy arrays can
be of any dimension. A brief demonstration follows:

>>> import numpy as np

# One dimensional array


>>> x = np.array([1, 2, 3])
>>> x
array([1, 2, 3])
>>> type(x)
<class 'numpy.ndarray'>
>>> A = np.array([[1, 2, 3], [4, 5,

6]]) >>> A array([[1, 2, 3],

[4, 5, 6]])
[Two-dimensional array]
# Shape of array
>>> x.shape
(3, ) # Tuple of single element

# 2D array, treated as 2 × 3 Matrix >>> A.shape


(2, 3)
NumPy arrays are vectorized. This means, mathematical operations over an
array will mean the same over each element. The mathematical operations
between two arrays will correspond to the same between corresponding
elements.

# Matrix Addition with NumPy

>>> A = np.array([[1, 2, 3], [4, 5, 6]])


>>> B = np.array([[5, 6, 4], [3, 4, 2]])
>>> A + B
array([[6, 8, 7],
[7, 9, 8]])

Note: The addition: A + B corresponds to the addition of corresponding


elements in them. The display of the resulting array (A + B) on interpreter
appears as a Matrix.
# Matrix product with NumPy

It should be noted that the product of two 2D arrays is not a matrix product.
Matrix product is a special kind of operation where we take row elements of
left matrix and multiply with corresponding column elements of the right
matrix. For matrix product, there is a NumPy method called dot().

(1 2 3 1 2
AB = 4 5 6) (3 4)
56

>>> A = np.array([[1, 2, 3], [4, 5, 6]])


>>> B = np.array([[1, 2], [3, 4], [5, 6]])
>>> np.dot(A, B)
array([[22, 28],
[49, 64]])

We can also write,


>>> A.dot(B)
array([[22, 28],

[49, 64]])
# Transpose by NumPy

>>> A = np.array([[1, 2, 3], [4, 5, 6]])


# array attribute
>>> A.T
array([[1, 4],
[2, 5],
[3, 6]])

# By NumpPy method >>> np.transpose(A) array([[1, 4],

[2, 5],
[3, 6]])
# Matrix object in NumPY

For a real mathematical looking operations, we may use a function called


matrix() in NumPy. With this, the 2D arrays are converted into matrix
objects.

>>> A = np.matrix([[1, 2, 3], [4, 5, 6]])

>>> B = np.matrix([[1, 2], [3, 4], [5, 6]])


>>> A*B # Matrix product
matrix([[22, 28],
[49, 64]])
>>> type(A)
<class 'numpy.matrix'>

Exercises
Write Python scripts for the following problems.

1. Write a function fib(n) in three different ways which generate Fibonacci


numbers below some given integer��.

2. Compute the prime factors of a given natural number.


3. Given three sides of a triangle, you are asked to determine whether
equilateral or isosceles.
4. Write a program for Determine how many steps it takes to reach 1.
5. Write a function to add two complex numbers. Also, in the same program,
write the triangle is

Collatz sequence. another function which adds the absolute values of the two
complex numbers.

6. Take a binary number ‘100110’ as a string and convert this into decimal
integer.
7. A ‘Harshad number’ is an integer that is divisible by the sum of its digits.
Define a functionharshad() to determine whether the given number is
Harshad number or not.
8. Count how many odd integers are there between 253 and 837.
9. Given a collection numbers: 10, 1, -10, 10, 4, 5, 0, 1, 9, 2, 0, prepare a list
containing all the duplicate numbers.
10. Calculate the following sum: ∑∞ 1 for��=0���� �� = 2 with an
accuracy level of 4 decimal places.
11. Find �� from: ��= 1 −1+1−1+1+ ⋯ ∞4 3 5 7 9 up to 6th decimal place
accurate.
12. Compute the following logarithmic series up to an accuracy of 3 decimal
places:

log�� (1−��)= 2 (�� +��3 ��5(1+��) +5+ ⋯ )3


13. You have taken a loan from Bank of the amount of Rs. 1 Lakh with
compound interest 10% for 10 years. Equated Monthly Interest (EMI) is paid
at the rate of Rs. 1000. How many years will it take to repay the entire loan?
Write a python program to find out the solution.

14. Write a computer program to compute the standard deviation (��) of


the numbers:−2, 0, 1, 6, 4, 3, 10, −3.5

15. Find all the divisors of the number 332.


16. Find the list of all the prime numbers between 1 and 1000. Also find the
density between every decade.
17. Check how many binary palindrome integers are there in the range that
corresponds to the decimal interval [1, 100]. [Note: For example, 11, 101,
11011 etc. are palindromes.]
18. Starting with ��1= 1, ��2= 1, generate 20 Fibonacci numbers with
the sequence����+1= ����+ ����−1 and then calculate the
following

series sum:
�� =∑
20 1
��=1����2

19. Legendre’s conjecture: It says that there is always one prime number
between the two consecutive natural numbers squared. For example, 2 and 3
are prime numbers between 12 and 22, 5 and 7 are between 22 and 32 and so
on. Write a code to test the conjecture between �� and �� + 1, with any
��.

20. Given the list of numbers: [0, 1, 1, 1, 0, 2, 3, 4, 3, 2, 1, -1, 5, 6, 1], find


out the most occurring number.

21. Given two lists: a = [1, 2, 7, 9, 11, 0, 21] and b = [3, 7, 1, 0, 12], write a
Python code to find out if there is at least one element common between
them.

22. Write a Python function that merges two lists [‘a’, ‘b’, ‘c’, ‘d’] and [1, 2,
3, 4]to form a list that takes alternate elements, [‘a’, 1, ‘b’, 2, ‘c’, 3, ‘d’, 4].

23. Consider the list: L = [0, 1, 2, 3, 2, 1, 3, −1, 0, 1, 2, 4] from some


measurement. Create another list which will contain the moving averages of
the numbers with window size 3.

24. Given the string, ‘aaabbaaaaaaabbccabbbaa’, write a

the longest Python function to find out

sequence of same characters. 25.Given a text: ‘A wiki is


publication collaboratively a hypertext edited and managed by its own
audience directly using a web browser.’, find out the most occurring
letter in it.
26. Take any positive integer number, large or
small. If the number is even, divide it by 2. If
the number is odd, multiply it by 3 and add 1.
Go on doing this way and check where you
end up. You will get a sequence of numbers.
This is Collatz sequence. Print the list of the
sequence of numbers.
27. Play a probabilistic game: Start with Rs.
1000. With win, capital is increased by RS. 1
and with losing it goes down by Rs. 1. Toss
a biased coin no. 1 with win probability ��1 =
1/10– 0.01 if the capital at hand is multiple of
3. If not, toss another biased coin with win probability ��2= 3/4– 0.01.
Print for 10 steps, how the capital goes with number of coin
tossing’s.
28. Tower of Hanoi Problem:
Tower of Hanoi is a mathematical puzzle where we have three rods and n
disks. To start with, the disks are stacked size wise (as in the adjacent image),
over one rod so that the biggest one is at the bottom and the smallest on the
top. The objective of the puzzle is to move the entire stack to the third rod
with the help of one auxiliary rod in the middle.

The rules:
• Move one disk at a time.
• Move only the top most disk from

a stack.

• No disk should be placed on top of a smaller disk.


[Pic Source:
www.ocf.berkeley.edu/~shidi/cs61a/wiki/Tow ers_of_Hanoi]

On Matrix

29. Take the array: A = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]. Reshape the array
into a 3 by 4 matrix. Transpose the matrix.

30. Make a 4 × 4 matrix with random elements (Use random number


generators).
31. Find out the trace of the matrix in Q.18
32. Find out the sum of all rows in the above matrix and check which sum is
minimum.
33.Consider the string: ‘123456789’. Make it a list of integers. From that list
make a square matrix (2 dimensional NumPy array).
34. Multiply two matrices: A = [[1, -2, 3], [2, 1, 5]] and B = [[4], [3], [2]]
35. Consider the 2-dimensional array: [[1, 3, 2], [5, 9, 8], [11, 0, 5]]. Write a
python script (without using NumPy) so to display the array in the form of a
matrix. Also, print the first column of the matrix.
36. Given a matrix: A = [[1, 2, 3], [4, 5, 6], [7, 8, 9]], make another matrix B
whose elements are the square root of the corresponding elements in A.
Compute ��. ����.
37. Write a Python program which takes 2 digits (7, 8) as input and generates
a twodimensional 7 × 8 array with �� = 0, 1, 2, … 6 and �� = 0, 1, 2, …
7 where the elements are �� ∗ ��.
38. Write a code with core Python to find out the trace, ����(��) of
the following matrix:�� = 3 4 8
(5 9 2)
160
39. Create two 3 × 3 matrices A and B with elements generated by a uniform
random number generator. Now establish the following:
(i) (�� + ��)��= ����+ ����
(ii) (����)��= ��������
40. Consider the matrix, �� = (1 23 4) which

satisfies the equation, ��(��)= ��2− 5�� − 2. Find out��4.


MCQ Test
# On Core Python

1. Which of the following are all immutable? (a)List, Tuple (b)Tuple, String
(c) String, List (d) List, Dictionary

2. Given, X = [-1, 1, 2, 1, 3, 5, 0,
-1], what is X[-1] * X[1] = ? (a)0 (b) 1 (c) −1 (d) 2

3. Given, L = [‘phys’, ‘chem’, ‘math’], which of the following is true?


(a)L[0][1] = L[1][1] = L[2][3] (b) L[1][0] = L[1][1] = L[2][3] (c) L[0][1] =
L[1][1] = L[3][2] (d) L[1][0] = L[0][1] = L[2][3]

4. What will be the output list if we write the following list comprehension?

x = [i**2 for i in range(6) if i%2 == 0]


(a) [2, 4, 16] (b) [0, 4, 16] (c) [0, 4, 6] (d) None

5. What is the output of the following program? x = 1234


for n in x:

print(n)
(a)1234 (b) 1, 2, 3, 4 (c) 1 2 3 4 (d) Error

6. What is the output of list(range(4, 14)[::3])?


(a)[4, 6, 8] (b) [4, 7, 10] (c) [4, 7, 10, 13] (d) Error

7. Given an object:x = [[([1,2,3])]], what can we write to extract the number


3?
(a) X[0][2] (b) x[0][0][2] (c) x[0][0][0][2] (d) x[0][0,2]

8. Suppose we write the following on interpreter: A = [0]


for i in range(4):

A.append(range(i))
print(A)
What can be the output?
(a) [[0], [0,1], [1,2], [3,4]] (b)

[0, [], [0], [0, 1], [0, 1,


2]] (c) [[0], [0, 1], [0, 1,
2]] (d) [0, [0], [0, 1], [0,
1, 2]]
9. Output of the following:
>>> a = -3.278
>>> from math import ceil, floor >>> int(a), abs(a), ceil(a), floor(a) (a) -3,
3.728, -3.0, -4.0 (b) -3, 3.278, -4.0, 3.0 (c) -3, 3.278, 4.0, 3.0 (d) -3, 3.278,
3.0, 4.0

10. What is the output of the following? S = ‘abd123’


S[2] = c
print(S)

(a) ‘abcd123’ (b) ‘acbd123’ (c) abcd123 (d) None

11. I want to write the following:


‘Hello! My age is 20.’
Which of the following is right?

(a) “Hello!” + “ “ + ”My age is“ + “ “ + “20.”


(b) “Hello!” + ”My age is“ + “20.”
(c) “Hello!” + “ “ + ”My age is“ + “ “ + 20.
(d) “Hello!” + ”My age is “ + “20.”
12. Consider the following Python script:
import math
x = str(round(math.pi, 4))
for k in x:
print(k, end = ‘’)

What will be the output?


(a) 3.141 (b) 3.1416 (c) 3.14159 (d) 31416
13. What is the output of the following?

A = [[0.5, 0.4, 0.2], [0.1, 0.2, 0.3], [0.5, 0.6, 0.3]] print(A[len(A)-1][1])
(a) 0.5 (b) 0.3 (c) 0.6 (d) 0.4

14. What will be the result of12 == 12.0? (a) True (b) False (c) 12 (d) 12.0
15. What will be the output of the following
Python Script?
x1, x2 = [0, 1]
for i in range(5):

x3 = x1 + x2
x1, x2 = x2, x3
print(x3)
(a) 5 (b) 3 (c) 13 (d) 8
16. What will be the output of the following? X = [i**2 for i in range(1,
4)]*2 print (X)
(a) [1, 4, 9] (b) [1, 4, 9, 16] (c) [2, 8, 18] (d) None
17. What is the type of the object is x?

x = ((2,3), [2,3], {2,3})


(a) set (b) tuple (c) list (d) dictionary

18. What will be the output of the following python code?


for i in range(0):

print(i)
(a) 0 (b) no output (c) error (d)

None
19. Which of the following symbols are used for comments in Python?
(a)// (b) “ (c) /**/ (d) #

20. What is not the correct way to represent a complex number?


(a)2+3j (b) complex(2,3) (c) 3j+2 (d) complx(2,3)

21. Result of the following?


>>> L = [2, 1, 0, 2.0, 0.5, 1] >>> list(set(filter(lambda x: x

>= 2, L))) (a) {2} (b) {2, 2.0} (c) [2] (d) [2.0]
22. Output of the following two lines:
>>> f = lambda x: x*x

>>> map(f, [i*0.1 for i in range(4)])


(a) [0, 1, 4, 9] (b) [0.1, 0.2, 0.3, 0.4] (c) [0.0, 0.01, 0.4, 0.09] (d) None 23.
Output of the following:
>>> def func(x, y):
return (x+y)/(x-y)
>>> f = lambda x,y: 5*func(x, y)
>>> f(3, 2)
(a) 25 (b) 5 (c) 0.5 (d) None 24. What can be the result of the following?
[1, 2, 3] == [1.0, 1.999, 3.0]
(a) True (b) False (c) [True, False, True] (d) [True, True, True]
25. What will be the output of the following?
>>> a, b = {1:4, 2:5}
>>> a, b = b, a
>>> a, b
(a) (1, 2) (b) {1, 2} (c) (4, 5) (d) (2, 1)

Appendix
Python Installation
On Windows:
Step #1: Go to Official Python site
(www.python.org) and click ‘Downloads’.
Step #2: Click the ‘view the full list of downloads’ (below the latest version
showing).
Step #3: Click ‘Download’ on the same row (3rd column) of any stable
release (3rd column). Python 3.8.3, for example.

Step #4: Scroll down on this page to see the options.


Step #5: Choose an option from the list. For example, if your computer is a
64-bit machine (default), you may click ‘Windows x86-64 executable
installer’ to download the executable file. The ‘python-3.8.3x.exe’
executable file is now downloaded.

Step #6: Now click the downloaded file to install Python 3.8.3. It will create
a Python directory automatically.
Step #7:Type ‘python’ on the search bar (at the bottom left of the screen) to
see that an IDLE (Python 3.8 64-bit) logo is created. You can create a
shortcut to the Windows taskbar or bring to Desktop by clicking ‘open file
location’. The IDLE logo now appears on the Desktop or taskbar.

Step #8: Click the IDLE logo. The IDLE interpreter Window (Python 3.8.3
Shell) will appear. Begin to type.
# External Python packages

Step #1: Click ‘file’ on the top left corner of the IDLE window, click ‘open’
to see the pathname to the Python directory that is created (location of the
files). In this case, it is: C:\Users\Abhijit Kar
Gupta\AppData\Local\Programs\Python\Python3 8. [You may copy this or
note down.]
Step #2: On the Window search (at the bottom left of screen) type ‘cmd’. A
black DOS window appears. Now copy paste pathname or type ‘cd
AppData\Local\Programs\Python\Python38’ on the command line and press
Enter [cd = change directory]. Now it shows the command line with full
pathname:

C:\Users\Abhijit Kar Gupta\


AppData\Local\Programs\Python\Python38>
Step #3: You can now type ‘dir’ to check what are the files and sub-
directories under the Python directory. Now go to the subdirectory ‘Scripts’
by typing:C:\...> cd scripts

Step #4: Final step!


To installNumPy, type:
C:\...Scripts> pip install numpy

Likewise, you can install any external package with ‘pip install’ command.
You can install many packages together by typing pip install numpy scipy
matplotlib.

[The machine should be connected to internet during this installation. To


come out of black window: Type ‘exit’ or click the cross to dismiss.

On Linux:
On most of the Linux distributions, Python 2 is preinstalled. Python 3.8 does
not come by default on Ubuntu or other Linux platforms. To install, open
terminal application and type the following:

$ sudo apt-get update


$ sudo apt-get install python3.8 python3-pip

On Android phone:

There are several Python App’s available. Any App of your choice can be
installed on mobile phone from Google play store. Some of the versions are
lighter (takes less space on your phone and so quick). You may do most of
the useful python calculations and testing over any kind of android phone.
External packages can also be done from the App’s repository. Some useful
App’s a re QPython, Pydroid etc.

Python Distributions:

There are various Python distributions that come with core Python along with
some common external packages. For example, Python(x, y), Anaconda etc.
Google search and install any of them as you wish. Each of them contains
various Python development environments: IPython Shells, Spyder, Jupyter
Notebook etc.

External Packages:

• NumPy (Numerical Python) is for array manipulation and more.


• SciPy (Scientific Python) contains various scientific modules
• Matplotlib (Mathematical Plotting Library) is to draw graphs
• Pandas is to deal with structured data. For Data Science and Machine
Learning, we may install ‘Scikit-learn’ to access raw data and for testing,
‘seaborn’ for data visualization and statistical graphics.

Consult the following blog article for installation on Windows, Mac, and
Linux:
realpython.com/installing-python

Python Versions
Python has two parallel versions: Python 3.x and Python 2.x. This book is
written with Python 3.x syntaxes.

In Python 3.x, the strings are in unicode whereas in Python 2.x, the strings
are stored as ASCII. At the user level, there are only a few differences
between the two versions. The installation of either of the versions is not
difficult. Also, two parallel versions can coexist on a same machine. One can
work in any version and can switch over to another version with ease where
only a few changes in syntaxes are to be made.

# Some user-level differences


Python 3.x Python 2.x

print print(‘Pyth on String’) print ‘Python String’

print(x, y) print x, y
input x = input() x = input()

The input() returns a


string.
The input() returns the type as given.

Another type, raw_input() returns a


string.

division 5/2 = 2.5 = 2 (quotient only)

range The range() function creates a range of numbers.

5.0/2 = 2.5 [For result in fraction, At least one of numerator or denominator


must be in
float.]
The range() function returns a list.

The type is range. It can be converted to a list or tuple.

Another kind, xrange() is equivalent to range() in Python 3.x.

Most of the releases in Python 2.x are stable. The latest releases/ versions in
Python 3.x are in developing stages. In short, Python 2.x is a legacy and
Python 3.x is the future.
input() in Python 2.x and Python 3.x:

# Python 2.x

x, y, z = input('Enter values \n') Enter values


2, -3, 4.5

# Python 3.x

x, y, z = eval(input('Enter val \n')) Enter val


2, -3, 4.5

In Python 3.x, every input is treated as string. [The raw_input() function of


Python 2.x is equivalent to input()in Python 3.x.]

>>> a = input('Enter values \n') Enter values


2, -3, 4.5
>>> a # This is a string '2, -3, 4.5'

To get the numerical value of the input variable, we need to evaluate the
input string through eval() function. Also, for multiple inputs, we need to
split the string through split() or we applyeval() and then unpack.

>>> x, y, z = eval(a) >>> x


2
>>> y
-3
>>> z
4.5

Bibliography

1. Python Official Site:www.python.org


2. NumPy, SciPy, Matplotlib: numpy.org, scipy.org, matplotlib.org
3. For mathematics:
mathworld.wolfram.com
4. Official Python Blog:
www.python.org/blogs/
5. List of various Python Blogs: github.com/mikeyny/awesomepython-blogs
6. Classified resources for Learning Python: docs.python
guide.org/intro/learning/
7. Python Tutorial Release 3.2.3 – Guido van Rossum, Fred L. Drake, Jr.,
editor (Python Software Foundation)
8. Python Tutorial Release 2.4.2 – Guido van Rossum, Fred L. Drake, Jr.,
editor (Python Software Foundation)
9. Scientific Computing in Python (1st ed.) – Abhijit Kar Gupta (Techno
World, Kolkata)
10. Think Python: How to Think like a Computer Scientist– Allen Downey
(Green Tea Press)
11. Learn Python the Hard Way – Zed Shaw (www.it-ebooks.info)
12. Computational Physics with Python – Dr. Eric Ayars (California State
University, Chico)
13. Computational Physics (Problem Solving with Python)– Rubin H.
Landau, Manuel J. P��́ez and Cristian C. Bordeianu (WileyVCH), 3rd
completely revised edition.
14. Numerical Mathematical Analysis– James B. Scarborough (Oxford
University Press)
15. Introductory Methods of Numerical Analysis– S.S. Sastry (Prentice Hall
of India Pvt. Ltd., New Delhi)
16. Computational Mathematics – B.P. Demidovich, I.A. Maron (MIR
Publishers, Moscow)
17. Numerical Recipes: The Art of Scientific Computing - W.H. Press, B.P.
Flannery, S.A. Teukolsky, W.T. Vetterling (Cambridge University Press)

18. Numeric Computing in Fortran – Sujit Kumar Bose (Narosa Publishing


House, India)

19. Python Programming– Ch Satyanarayana, M Radhika Mani, B N


Jagadesh (Universities Press, India)

20. Data Science from Scratch – Joel Grus (O’REILLY)


21. Numerical Methods in Engineering with Python 3 – Jaan Kiusalaas
(CAMBRIDGE)
22. Python Programming Using Problem Solving Approach– Reema Thareja
(Oxford University Press)
23. Introduction to Computational Physics for Undergraduates– Omair
Zubairi, Fridolin Weber (IOP Concise Physics)
24. Introduction to Scientific Computing in Python – Robert Johansson (PDF
in internet)
25. Introduction to Python for Computational Science and Engineering (A
beginner’s guide) – Hans Fangohr (Faculty of Engineering and the
Environment, University of Southampton) [pdf copy available in internet]
26. From Calculus to Chaos– An Introduction to Dynamics by David
Acheson (Oxford)
27. Solving The Stationary One Dimensional Schrödinger Equation With The
Shooting Method– T U Wien (Bachelor Thesis) [pdf copy available on
internet].
28. Python Programming and Numerical Methods – A Guide for Engineers
and Scientists by Qingkai Kong, Timmu Siauw, Alexandre Bayen (AP)
29. The One-Dimensional Finite-Difference Time-Domain (FDTD) Algorithm
Applied to the Schrödinger Equation– James R. Nagel [pdf copy from
internet]
30. Elementary Thermal Physics Using Python
– Anders Malthe-Sorenssen (Springer)
31. Rutgers Graduate Program:
(www.physics.rutgers.edu/grad/509/src_pr og/hmw/Hydrogen.html)
32. Quantum Mechanics with the Python (Helentronica Blog)

Index

2D array, 345
3D Bar Plot, 524
3D Plot, 520, 558

Anaconda, 446 append, 158 Append, 349 arange, 317


argsort, 370
astype, 316
Attributes, 233
B

bin, 37
Binary, 38
Binary Search, 634 Boolean, 7
Broadcasting, 374 Bubble Sort, 599 builtins, 31

Calculator, 21
Calculus, 546
Calendar, 84
Cardoid, 472
Child Class, 249
Class, 233
Coiling Squares, 276 Collatz Sequence, 621 Colors, 455
Commenting, 52
Comparison of

Numbers, 25
complex, 31
Complex, 7
Complex Math

Module, 67
Complex Matrices, 422 Complex Matrix, 380 Concatenate, 351
Concatenation, 191 Contour Plot, 506
Conversion, 192
Correlation

Coefficient, 609
Cosine Series, 614
Cross Product, 390

Data Type, 309


DataFrame, 575
Date, 84
decimal, 34
Deep copy, 224
Define a Function, 116 Delete Element, 344 Deleting Objects, 247
Derivative, 414
diag, 386
diagonalize, 545
Dictionary, 203
Dictionary

Comprehension, 213

Differential Equations, 562


Differentiation, 547
Doc String, 242
downloaded data, 516
dtype, 315

empty, 382
Enumerate, 177 Evaluate, 43
Exponential Series,

612
eye, 383
F
factorial, 61
Fibonacci Numbers,

633
Fibonacci Spiral, 286 fill, 382
filter Function, 140 Flatten, 326
Flip, 377
Float, 7
Floor Division, 9 For-Loop, 92
Formatted, 45
Fractal Tree, 293 Fractals, 289
Frozen Set, 202
Function Overload,

136
G

GCD, 623
Geometrical, 560 Global, 131
Greek Letters, 478

Hello World, 10 hex, 38


Hexadecimal, 38 Histogram, 495
Horizontal line, 463 hsplit, 348
hstack, 354

Identities, 26
identity, 383
Identity, 28
immutable, 189
Imported Functions,

138
Indexing, 147, 173,
190, 332
Infinite Series, 612 Inheritance, 249
Inner product, 390 Input and Output, 39 Input from Outside, 42 insert, 162
Insertion Sort, 603 Integer, 7
Integral, 414
Integration, 549
Interpreter, 47
intersection, 201
intertools, 486
Iterator, 330
K
keys, 207
Koch Curve, 290
L

Lambda Function, 135 LCM, 623


Leap Year, 619
Legend, 460
Limit, 546
linalg, 417
Line Styles, 456
Linear Algebra, 417 linspace, 319
Lissajous, 470
List, 144
List Comprehension,

167
List Methods, 152 loadtxt, 429
Local, 131
logspace, 322
Long, 7
lookfor, 303
Loops, 92
Max, 595
Mean, 608
meshgrid, 425
Methods, 192, 196,

233
Methods on String,
180
mgrid, 427
Min, 595
Module, 54
Modulo, 9, 23
monochrome, 455 Multiple Figures, 488 mutable, 204

M
map Function, 139
Marker, 455
Marker Styles, 456
Math module, 59
Math Symbols, 480 Mathematical
Operators, 8
Matplotlib, 446
Matrix, 379, 541
Matrix Operations, 638

Namespace, 16
Nested Dictionary, 215 Nested List, 170
normalvariate, 77
Number Conversion,

627
Numbers, 6, 24
NumPy Arrays, 302

Object, 233 oct, 38


Octal, 38
ones, 384
Operators, 8

Package, 259
Packing, 217
Pandas, 569
Parent Class, 249 Perfect Square, 103 Pie Chart, 504
pivot_table, 517
Plotting in 2D, 447 Polar Plot, 471
Polygon, 272
Polynomial, 410
pop, 160
Prime Numbers, 589 Product, 362
Pythagorean Triplets,

625
Q
Quadratic Equation, 607
R
randint, 437
Random Choice, 78,

437
Random Sampling, 81 Random Shuffling, 82 Random Walk, 79, 281
randrange, 77
range, 94, 165
Ravel, 326
repeat, 389
Reshape, 324
Resize, 324
Roots, 412
Rotate, 377
round, 33

Save Figure, 477 Series, 570


Set, 194
Shallow copy, 223 Shape, 324
Size, 324
Slicing, 148, 175, 191,

333
sorted, 151
Sorting, 368
Special Arrays, 382 split, 184, 346
Split Arrays, 346
Stack plot, 500
Stacking Arrays, 352 Statistical Plots, 494 Statistics, 355
Std Deviation, 608 String, 172
Strong Numbers, 594 Subplot, 490
Subset, 202
Swap, 337
SymPy, 531

T
Tensor, 395

Tensor Product, 397 tensordot, 399


Time, 84
Tolerance, 616
Trace, 366
Transpose, 377, 645 Try Except, 108 Tuples, 189
Turtle, 261
Type, 26
Types, 26

uniform, 77, 434


union, 200
Unique Elements, 328 universal, 403
Unpacking, 217
urandom, 83

V
values, 207

Variables, 5
Variance, 608
Vectorize, 408
version, 56
Vertical line, 463 Void Function, 122 vsplit, 348 vstack, 354

W
While Loop, 101
X
X-Y Plots, 447

Z
zeros, 384
zip Function, 141

You might also like