Informatics Practices: Class 11 — Chapter 6: Introduction to NumPy

6.1 Introduction to NumPy

NumPy stands for Numerical Python . It is a core Python package designed for high-performance data analysis and scientific computing. It introduces a powerful multidimensional array object called ndarray , along with an extensive collection of functions and tools to operate on these arrays efficiently.

Technical Profile: NumPy Library Environment

  • Architecture Layer / Tier: High-performance scientific computing library with a compiled C/C++ backend engine.
  • Operational Mechanics: Utilizes homogeneous, contiguous memory blocks to perform vectorized mathematical operations, eliminating Python loop overhead.
  • System Impact & Execution Outputs: Drastically speeds up data processing pipelines and serves as the foundational data structure for pandas, scikit-learn, and other data science libraries.
Installing NumPy

NumPy can be installed in the system environment using the Python package installer ( pip ) via the command line interface:

  
  pip install NumPy

6.2 Array Concepts & Memory Allocation

An array is a data type used to store multiple values of the same type using a single identifier (variable name). It represents an ordered collection of data elements where each element can be referenced by its unique index (position).

Important Characteristics of an Array
  • Homogeneity: Each element of the array must be of the exact same data type, though the actual values stored may differ.
  • Contiguous Memory Allocation: The entire array is stored in adjacent, unbroken memory locations. This allows the system to calculate element addresses instantly, making operations extremely fast.
  • Indexed Referencing: Elements are identified using the array name combined with a unique integer index. Indexing is zero-based (starts at 0 and ends at n-1 for an array of size n ).

For example, consider an array containing 5 numbers: [ 10, 9, 99, 71, 90 ]

Element Value 10 9 99 71 90
Index (Positive) [0] [1] [2] [3] [4]
Contiguous vs. Non-Contiguous Memory Allocation

Memory Allocation Mechanics:

  • Contiguous Memory Allocation: The memory manager divides space into fixed-size, adjacent positions. Each position is allocated to a single data element sequentially. This allows direct offset calculations.
  • Non-Contiguous Memory Allocation: Data is divided into several blocks and placed in different scattered parts of the memory based on availability. Elements are linked via pointers, which introduces access overhead.
6.3 NumPy Array (ndarray) vs. Python List

While Python has a built-in list structure, it is not optimized for vector mathematics or large-scale numerical processing. NumPy's ndarray provides a highly efficient alternative.

6.3.1 Key Differences Between List and Array
Feature Python List NumPy Array (ndarray)
Data Homogeneity Can store elements of different data types. Example: [1, 3.4, 'hello', 'a@'] All elements must be of the same data type. Example: [1.2, 5.4, 2.7] (all floats)
Memory Storage Elements are not stored contiguously in memory; stores pointers to objects. Elements are stored in contiguous memory locations, enabling fast cache access.
Element-wise Operations Does not support element-wise operations directly (e.g., multiplying a list by 3 replicates the list). Supports direct element-wise operations (e.g., A1 / 3 divides each element by 3).
Memory Efficiency Consumes more space because it must store type information and pointers for every individual element. Highly memory-efficient; stores type information once for the entire array.
Library Origin Part of core Python. Part of the external NumPy library.
6.3.2 Creation of NumPy Arrays from Lists

To create and use NumPy arrays, the NumPy library must first be imported. By convention, it is imported under the alias np :

  
  >>> import numpy as np

The np.array() function converts a Python list into a NumPy array:

  
    >>> array1 = np.array([10, 20, 30])
    >>> array1
    array([10, 20, 30])
  

Creating a 1-D Array & Type Promotion

An array with a single row of elements is a 1-D array. If a list contains mixed data types, NumPy automatically promotes (coerces) all elements to a common compatible data type (e.g., integers and floats are promoted to strings if a string is present):

  
    >>> array2 = np.array([5, -7.4, 'a', 7.2])
    >>> array2
    array(['5', '-7.4', 'a', '7.2'], dtype='<U32')
  

Note: <U32 indicates a Unicode-32 data type.

Creating a 2-D Array

A two-dimensional (2-D) array is created by passing a nested list (a list of lists) to the np.array() function:

  
    >>> array3 = np.array([[2.4, 3], [4.91, 7], [0, -1]])
    >>> array3
    array([[ 2.4 ,  3.  ],
    [ 4.91,  7.  ],
    [ 0.  , -1.  ]])
  

Note: The integers 3, 7, 0, and -1 have been automatically promoted to floats to maintain homogeneity.

Common Syntax Mistake:

When passing elements to np.array() , you must enclose them in square brackets to pass them as a single list argument. Forgetting the brackets results in a TypeError.

  
    # INCORRECT:
    >>> a = np.array(1, 2, 3, 4)
    # CORRECT:
    >>> a = np.array([1, 2, 3, 4])
  
6.3.3 Attributes of a NumPy Array

NumPy arrays have several built-in attributes that describe their structural properties:

Attribute Description Example Code & Output
ndim Returns the number of dimensions (axes/rank) of the array.
    
      >>> array3.ndim
      2
    
  
shape Returns a tuple of integers indicating the size of the array along each dimension (rows, columns).
    
      >>> array3.shape
      (3, 2)
    
  
size Returns the total number of elements in the array (equal to the product of the elements of its shape).
    
      >>> array3.size
      6
    
  
dtype Returns the data type of the elements stored in the array.
    
      >>> array3.dtype
      dtype('float64')
    
  
itemsize Returns the size in bytes of each individual element in the array.
    
      >>> array3.itemsize
      8
    
  
6.3.4 Alternative Methods of Creating Arrays
  1. Specifying explicit data types: Use the dtype parameter during creation.
      
        >>> array4 = np.array([[1, 2], [3, 4]], dtype=float)
        >>> array4
        array([[1., 2.],
        [3., 4.]])
      
    
  2. Initializing with zeros: Use np.zeros(shape) to create an array filled with 0s (default type is float).
      
        >>> array5 = np.zeros((3, 4))
        >>> array5
        array([[0., 0., 0., 0.],
        [0., 0., 0., 0.],
        [0., 0., 0., 0.]])
      
    
  3. Initializing with ones: Use np.ones(shape) to create an array filled with 1s.
      
        >>> array6 = np.ones((3, 2))
        >>> array6
        array([[1., 1.],
        [1., 1.],
        [1., 1.]])
      
    
  4. Creating numerical ranges: Use np.arange([start,] stop[, step]) to generate sequences.
      
        >>> np.arange(6)
        array([0, 1, 2, 3, 4, 5])
        >>> np.arange(-2, 24, 4)
        array([-2,  2,  6, 10, 14, 18, 22])
      
    
6.4 Indexing and Slicing

NumPy arrays support precise indexing and slicing operations to access and extract subsets of data.

6.4.1 Indexing 2-D Arrays

For 2-D arrays, elements are referenced using two indices: [row_index, column_index] . Both indices start at 0.

Consider a 2-D array representing student marks: Student (Row Index) Maths (Col 0) English (Col 1) Science (Col 2) Ramesh (0) 78 67 56 Vedika (1) 76 75 47 Harun (2) 84 59 60 Prasad (3) 67 72 54

  
    >>> marks[0, 2]  # Row 0, Column 2 (Ramesh's Science mark)
    56
    >>> marks[0, 4]  # Out of bounds index
    IndexError: index 4 is out of bounds for axis 1 with size 3
  
6.4.2 Slicing Arrays

Slicing extracts a portion of an array using the syntax [start:end:step] . The end index is exclusive.

1-D Slicing Examples:

  
    >>> array8 = np.array([-2, 2, 6, 10, 14, 18, 22])
    >>> array8[3:5]
    array([10, 14])
    >>> array8[::-1]  # Reversing the array
    array([22, 18, 14, 10,  6,  2, -2])
  

2-D Slicing Examples:

Using a 3x4 array named array9 :

  
    >>> array9 = np.array([[-7, 0, 10, 20], [-5, 1, 40, 200], [-1, 1, 4, 30]])
    # Access all elements in the 3rd column (index 2) across rows 0 to 2
    >>> array9[0:3, 2]
    array([10, 40, 4])
    # Access elements of the 2nd and 3rd rows, from the 1st and 2nd columns
    >>> array9[1:3, 0:2]
    array([[-5,  1],
    [-1,  1]])
    # If row/column indices are omitted, all rows/columns are selected
    >>> array9[:, 2]
    array([10, 40, 4])
  
6.5 Operations on Arrays

NumPy supports fast element-wise arithmetic, matrix operations, transposing, and sorting.

6.5.1 Arithmetic Operations

Arithmetic operations on two arrays are performed element-by-element. The arrays must have the exact same shape.

  
    >>> array1 = np.array([[3, 6], [4, 2]])
    >>> array2 = np.array([[10, 20], [15, 12]])
    # Element-wise Addition
    >>> array1 + array2
    array([[13, 26],
    [19, 14]])
    # Element-wise Subtraction
    >>> array1 - array2
    array([[ -7, -14],
    [-11, -10]])
    # Element-wise Multiplication
    >>> array1 * array2
    array([[30, 120],
    [60, 24]])
    # Matrix Multiplication (Dot Product)
    >>> array1 @ array2
    array([[120, 132],
    [ 70, 104]])
    # Element-wise Exponentiation
    >>> array1 ** 3
    array([[ 27, 216],
    [ 64,   8]], dtype=int32)
    # Element-wise Division
    >>> array2 / array1
    array([[3.33333333, 3.33333333],
    [3.75      , 6.        ]])
    # Element-wise Modulo (Remainder)
    >>> array2 % array1
    array([[1, 2],
    [3, 0]], dtype=int32)
  
6.5.2 Transpose

Transposing an array swaps its rows and columns. The original array remains unchanged unless reassigned.

  
    >>> array3 = np.array([[10, -7, 0, 20], [-5, 1, 200, 40], [30, 1, -1, 4]])
    >>> array3.transpose()
    array([[ 10,  -5,  30],
    [ -7,   1,   1],
    [  0, 200,  -1],
    [ 20,  40,   4]])
  
6.5.3 Sorting Arrays

The sort() method sorts array elements in ascending order. For 2-D arrays, sorting can be performed along specific axes:

  • 1-D Sorting:
        
          >>> array4 = np.array([1, 0, 2, -3, 6, 8, 4, 7])
          >>> array4.sort()
          >>> array4
          array([-3,  0,  1,  2,  4,  6,  7,  8])
        
      
  • 2-D Sorting (Row-wise, axis=1): Sorts elements within each row independently.
      
        >>> array4 = np.array([[10, -7, 0, 20], [-5, 1, 200, 40], [30, 1, -1, 4]])
        >>> array4.sort()  # axis=1 is default
        >>> array4
        array([[ -7,   0,  10,  20],
        [ -5,   1,  40, 200],
        [ -1,   1,   4,  30]])
      
    
  • 2-D Sorting (Column-wise, axis=0): Sorts elements within each column independently.
      
        >>> array5 = np.array([[10, -7, 0, 20], [-5, 1, 200, 40], [30, 1, -1, 4]])
        >>> array5.sort(axis=0)
        >>> array5
        array([[ -5,  -7,  -1,   4],
        [ 10,   1,   0,  20],
        [ 30,   1, 200,  40]])
      
    
6.6 Concatenating, Reshaping, and Splitting Arrays

These structural operations allow you to combine, reshape, or divide arrays.

6.6 Concatenating Arrays

Concatenation joins multiple arrays along a specified axis using np.concatenate() . All dimensions except the concatenation axis must match exactly.

  
    >>> array1 = np.array([[10, 20], [-30, 40]])
    >>> array2 = np.zeros((2, 3), dtype=array1.dtype)
    # Concatenate along columns (axis=1)
    >>> np.concatenate((array1, array2), axis=1)
    array([[ 10,  20,   0,   0,   0],
    [-30,  40,   0,   0,   0]])
    # Attempting to concatenate along rows (axis=0) when column dimensions mismatch:
    >>> np.concatenate((array1, array2), axis=0)
    ValueError: all the input array dimensions except for the concatenation axis must match exactly
  
6.7 Reshaping Arrays

The reshape() function changes the dimensions of an array without changing its data. The total number of elements must remain constant.

  
    >>> array3 = np.arange(10, 22)  # 12 elements
    >>> array3.reshape(3, 4)
    array([[10, 11, 12, 13],
    [14, 15, 16, 17],
    [18, 19, 20, 21]])
    >>> array3.reshape(2, 6)
    array([[10, 11, 12, 13, 14, 15],
    [16, 17, 18, 19, 20, 21]])
  
6.8 Splitting Arrays

The np.split() function divides an array into multiple sub-arrays along a specified axis. You can split by specifying split indices or an integer for equal divisions.

  
    >>> array4 = np.array([[10, -7, 0, 20], [-5, 1, 200, 40], [30, 1, -1, 4], [1, 2, 0, 4], [0, 1, 0, 2]])
    # Split row-wise (axis=0) at row indices 1 and 3
    >>> first, second, third = np.split(array4, [1, 3])
    >>> first
    array([[10, -7,  0, 20]])
    >>> second
    array([[-5,   1, 200,  40],
    [30,   1,  -1,   4]])
    >>> third
    array([[1, 2, 0, 4],
    [0, 1, 0, 2]])
    # Split column-wise (axis=1) into 2 equal parts
    >>> firsthalf, secondhalf = np.split(array4, 2, axis=1)
    >>> firsthalf
    array([[10, -7],
    [-5,  1],
    [30,  1],
    [ 1,  2],
    [ 0,  1]])
  
6.9 Statistical Operations on Arrays

NumPy provides built-in statistical functions to compute descriptive metrics across an entire array or along specific axes.

Core Statistical Functions

Let's use these two sample arrays:

  
    >>> arrayA = np.array([1, 0, 2, -3, 6, 8, 4, 7])
    >>> arrayB = np.array([[3, 6], [4, 2]])
  
  1. Maximum Value ( max() ): Finds the largest element.
      
        >>> arrayA.max()
        8
        >>> arrayB.max(axis=1)  # Row-wise maximums
        array([6, 4])
        >>> arrayB.max(axis=0)  # Column-wise maximums
        array([4, 6])
      
    
  2. Minimum Value ( min() ): Finds the smallest element.
      
        >>> arrayA.min()
        -3
        >>> arrayB.min(axis=0)
        array([3, 2])
      
    
  3. Summation ( sum() ): Computes the sum of elements.
      
        >>> arrayA.sum()
        25
        >>> arrayB.sum(axis=1)
        array([9, 6])
      
    
  4. Arithmetic Mean ( mean() ): Computes the average value.
      
        >>> arrayA.mean()
        3.125
        >>> arrayB.mean(axis=0)
        array([3.5, 4. ])
      
    
  5. Standard Deviation ( std() ): Measures the dispersion of elements from the mean.
      
        >>> arrayA.std()
        3.550968177835448
        >>> arrayB.std(axis=0)
        array([0.5, 2. ])
      
    
6.10 Loading and Saving Arrays from Files

NumPy provides robust functions to read data from external \text files (like CSVs) and write arrays back to disk.

6.10.1 Loading Data with loadtxt()

The np.loadtxt() function is used to load homogeneous data from \text files. It requires each row to have the same number of values.

Suppose we have a file named data.txt :

  
    RollNo, Marks1, Marks2, Marks3
    1, 36, 18, 57
    2, 22, 23, 45
    3, 43, 51, 37
    4, 41, 40, 60
    5, 13, 18, 37
  

We can load this file into a NumPy array using:

  
    >>> studentdata = np.loadtxt('C:/NCERT/data.txt', skiprows=1, delimiter=',', dtype=int)
    >>> studentdata
    array([[ 1, 36, 18, 57],
    [ 2, 22, 23, 45],
    [ 3, 43, 51, 37],
    [ 4, 41, 40, 60],
    [ 5, 13, 18, 27]])
  

Key Parameters of loadtxt():

  • skiprows=1: Skips the first row (header row containing column names).
  • delimiter=',': Specifies the character separating values in the file (e.g., comma for CSV).
  • dtype=int: Specifies the data type of the resulting array (defaults to float).
  • unpack=True: Transposes the returned array, allowing you to extract columns directly into separate variables.
      
      >>> rollno, mks1, mks2, mks3 = np.loadtxt('C:/NCERT/data.txt', skiprows=1, delimiter=',', unpack=True, dtype=int)
    
    
6.10.2 Loading Data with genfromtxt()

The np.genfromtxt() function is more robust than loadtxt() because it can handle missing values and non-numeric data, converting them to nan (Not a Number).

Suppose we have a file named dataMissing.txt :

  
    RollNo, Marks1, Marks2, Marks3
    1, 36, 18, 57
    2, ab, 23, 45
    3, 43, 51,
    4, 41, 40, 60
    5, 13, 18, 27
  

Loading this file with default settings:

  
    >>> dataarray = np.genfromtxt('C:/NCERT/dataMissing.txt', skip_header=1, delimiter=',')
    >>> dataarray
    array([[ 1., 36., 18., 57.],
    [ 2., nan, 23., 45.],
    [ 3., 43., 51., nan],
    [ 4., 41., 40., 60.],
    [ 5., 13., 18., 27.]])
  

Handling Missing Values with filling_values:

You can replace missing or non-numeric values with a specific placeholder value using the filling_values parameter:

  
    >>> dataarray = np.genfromtxt('C:/NCERT/dataMissing.txt', skip_header=1, delimiter=',', filling_values=-999, dtype=int)
    >>> dataarray
    array([[   1,   36,   18,   57],
    [   2, -999,   23,   45],
    [   3,   43,   51, -999],
    [   4,   41,   40,   60],
    [   5,   13,   18,   27]])
  
6.11 Saving Arrays to Disk

The np.savetxt() function writes a NumPy array to a \text file on disk.

  
  >>> np.savetxt('C:/NCERT/testout.txt', studentdata, delimiter=',', fmt='%i')

  • fmt='%i': Saves the data as integers. The default format is float.
File I/O Method Reference Matrix
Computing Method Exact Syntactic Execution Signature Supported Parameters & Arguments Algorithmic Purpose & Output Representation
loadtxt numpy.loadtxt(fname, dtype=<class 'float'>, comments='#', delimiter=None, converters=None, skiprows=0, usecols=None, unpack=False, ndmin=0, encoding='bytes', max_rows=None) fname , dtype , delimiter , skiprows , unpack Loads homogeneous data from a \text file into a NumPy array. Fast but fails on missing values.
genfromtxt numpy.genfromtxt(fname, dtype=<class 'float'>, comments='#', delimiter=None, skip_header=0, skip_footer=0, converters=None, missing_values=None, filling_values=None, usecols=None, names=None, unpack=None, usemask=False) fname , dtype , delimiter , skip_header , filling_values , unpack Loads data from a \text file, automatically handling missing or invalid values by converting them to nan or a specified placeholder.
savetxt numpy.savetxt(fname, X, fmt='%.18e', delimiter=' ', newline=' ', header='', footer='', comments='# ', encoding=None) fname , X , fmt , delimiter Saves a NumPy array to a \text file using a specified delimiter and format.
6.12 Case Study: Iris Flower Dataset Analysis

The Iris flower dataset is a classic multivariate dataset introduced by the British statistician and biologist Ronald Fisher in 1936. It consists of 50 samples from each of three species of Iris (Iris setosa, Iris virginica, and Iris versicolor). Four features are measured from each sample: the length and the width of the sepals and petals, in centimeters.


Socio-Technical Analysis & Core Systemic Impact: This dataset serves as a foundational benchmark for pattern recognition and classification algorithms. In modern data pipelines, handling such multi-class biological data requires robust preprocessing protocols, including dropping non-numeric categorical columns (like the species name string) to perform matrix operations, splitting arrays cleanly by class indices, and computing descriptive statistics (mean, standard deviation, variance) to establish classification boundaries without algorithmic bias.

Case Study Implementation Code
  
    >>> import numpy as np
    # 1. Load the data from Iris.txt (ignoring the header and string column)
    >>> iris = np.genfromtxt('C:/NCERT/Iris.txt', skip_header=1, delimiter=',', dtype=float)
    # 2. Drop column index 4 (the species name string column) to keep only numeric data
    >>> iris = iris[0:30, [0, 1, 2, 3, 5]]
    # 3. Display shape, dimensions, and size
    >>> iris.shape
    (30, 5)
    >>> iris.ndim
    2
    >>> iris.size
    150
    # 4. Split the dataset into three arrays (10 rows each for the three species)
    >>> iris1, iris2, iris3 = np.split(iris, [10, 20], axis=0)
    # 5. Find column-wise statistics for the entire dataset
    >>> iris_max = iris.max(axis=0)
    >>> iris_min = iris.min(axis=0)
    >>> iris_avg = iris.mean(axis=0).round(2)
    >>> iris_std = iris.std(axis=0).round(2)
    >>> iris_avg
    array([5.68, 3.03, 3.61, 1.22, 2.  ])
    # 6. Compare species statistics (e.g., check if Iris setosa's min sepal length is greater than the dataset's overall min)
    >>> iris1_min = iris1.min(axis=0)
    >>> iris1_min[0] > iris_min[0]
    False
    # 7. Save the computed average values to disk
    >>> np.savetxt('C:/NCERT/IrisMeanValues.txt', iris_avg, delimiter=',')