Numpy

NumPy is a Python library used for working with arrays. It also has functions for working in domain of linear algebra, fourier transform, and matrices. NumPy was created in 2005 by Travis Oliphant. It is an open source project and you can use it freely. NumPy stands for Numerical Python.

Java
    In Python we have lists that serve the purpose of arrays, but they are slow to process. NumPy aims to provide an array object that is up to 50x faster than traditional Python lists. The array object in NumPy is called ndarray, it provides a lot of supporting functions that make working with ndarray very easy. Arrays are very frequently used in data science, where speed and resources are very important. Why is NumPy Faster Than Lists? NumPy arrays are stored at one continuous place in memory unlike lists, so processes can access and manipulate them very efficiently. This behavior is called locality of reference in computer science. This is the main reason why NumPy is faster than lists. Also it is optimized to work with latest CPU architectures. Which Language is NumPy written in? NumPy is a Python library and is written partially in Python, but most of the parts that require fast computation are written in C or C++.
  1. Installation of NumPy
             
                pip install numpy
                
    
  2. Import NumPy
             
                import numpy
                
    
         
            import numpy as np
            
    
  3. Checking NumPy Version
             
                import numpy as np            print(np.__version__)
                
    
  4. Create a NumPy ndarray Object
    Create a NumPy ndarray Object
    NumPy is used to work with arrays. The array object in NumPy is called ndarray.

    We can create a NumPy ndarray object by using the array() function.

    type(): This built-in Python function tells us the type of the object passed to it. Like in above code it shows that arr is numpy.ndarray type.
                         
                            arr = np.array([1, 2, 3, 4, 5])                        print(arr)
                            
    
  5. Dimensions in Arrays
    Dimensions in Arrays
    A dimension in arrays is one level of array depth (nested arrays).

    nested array: are arrays that have arrays as their elements.

    0-D arrays, or Scalars, are the elements in an array. Each value in an array is a 0-D array.
                                                       
    arr = np.array(42)
                                        
                            

    An array that has 0-D arrays as its elements is called uni-dimensional or 1-D array.
                                                       
                                        arr = np.array([1, 2, 3, 4, 5])                                    
                            

    An array that has 1-D arrays as its elements is called a 2-D array.
                                                       
                                        arr = np.array([[1, 2, 3], [4, 5, 6]])
                                        
                            

    An array that has 2-D arrays (matrices) as its elements is called 3-D array.
                                                         
                                        arr = np.array([[[1, 2, 3], [4, 5, 6]], [[1, 2, 3], [4, 5, 6]]])
                                          
                              
  6. Check Number of Dimensions
    Check Number of Dimensions
    NumPy Arrays provides the ndim attribute that returns an integer that tells us how many dimensions the array have.
        
            a = np.array(42)
            print(a.ndim)
    
    
  7. Access Array Elements
    Access Array Elements
    Array indexing is the same as accessing an array element.
    You can access an array element by referring to its index number.
    The indexes in NumPy arrays start with 0, meaning that the first element has index 0, and the second has index 1 etc.
        
            arr = np.array([1, 2, 3, 4])
    print(arr[0])
    
    

    Access 2-D Arrays

    
        arr = np.array([[1,2,3,4,5], [6,7,8,9,10]])print('2nd element on 1st row: ', arr[0, 1])
    
    

    Negative Indexing(Use negative indexing to access an array from the end.)

    
        arr = np.array([[1,2,3,4,5], [6,7,8,9,10]])print('Last element from 2nd dim: ', arr[1, -1])
    
    
  8. Slicing arrays
    Slicing arrays
    Slicing in python means taking elements from one given index to another given index.

    We pass slice instead of index like this: [start:end].
    We can also define the step, like this: [start:end:step].
    If we don't pass start its considered 0
    If we don't pass end its considered length of array in that dimension
    If we don't pass step its considered 1
    Note: The result includes the start index, but excludes the end index.

    Slicing

    
        arr = np.array([1, 2, 3, 4, 5, 6, 7])print(arr[:4])
    
    

    Use the step value to determine the step of the slicing

    
        arr = np.array([1, 2, 3, 4, 5, 6, 7])print(arr[1:5:2])
    
    
  9. Data Types in NumPy
    Java Data Types
    Data Types in NumPy NumPy has some extra data types, and refer to data types with one character, like i for integers, u for unsigned integers etc. Below is a list of all data types in NumPy and the characters used to represent them. i - integer b - boolean u - unsigned integer f - float c - complex float m - timedelta M - datetime O - object S - string U - unicode string V - fixed chunk of memory for other type ( void )

                                    
                                        arr = np.array([1, 2, 3, 4])                                    print(arr.dtype)
                                
                                

                                    
                                        arr = np.array([1, 2, 3, 4], dtype='S')
                                
                                

    The astype() function creates a copy of the array, and allows you to specify the data type as a parameter.

                                    
                                        arr = np.array([1.1, 2.1, 3.1])newarr = arr.astype('i');      
                                
                                
  10. NumPy Array Copy vs View
    NumPy Array Copy vs View
    The Difference Between Copy and View
    The main difference between a copy and a view of an array is that the copy is a new array, and the view is just a view of the original array.

    The copy owns the data and any changes made to the copy will not affect original array, and any changes made to the original array will not affect the copy.


    The view does not own the data and any changes made to the view will affect the original array, and any changes made to the original array will affect the view.

    The copy SHOULD NOT be affected by the changes made to the original array.

    The view SHOULD be affected by the changes made to the original array.The original array SHOULD be affected by the changes made to the view.


    Check if Array Owns its Data As mentioned above, copies owns the data, and views does not own the data, but how can we check this?

    Every NumPy array has the attribute base that returns None if the array owns the data.
    Otherwise, the base attribute refers to the original object.

                                    
                                        arr = np.array([1, 2, 3, 4, 5])
                                        x = arr.copy()
                                
                                

                                    
                                        arr = np.array([1, 2, 3, 4, 5])
                                        x = arr.view()
                                
                                

    The copy returns None. The view returns the original array.

                                    
                                        arr = np.array([1, 2, 3, 4, 5])                                    x = arr.copy()
                                        y = arr.view()
                                        
                                        print(x.base)
                                        print(y.base)
                                
                                
  11. NumPy Array Shape
    NumPy Array Shape
    Shape of an Array
    The shape of an array is the number of elements in each dimension.


    Get the Shape of an Array
    NumPy arrays have an attribute called shape that returns a tuple with each index having the number of corresponding elements.

                        
                            arr = np.array([[1, 2, 3, 4], [5, 6, 7, 8]])                        print(arr.shape)
                    
                    
  12. Array Reshaping
    Array Reshaping
    Reshaping arrays Reshaping means changing the shape of an array. The shape of an array is the number of elements in each dimension. By reshaping we can add or remove dimensions or change number of elements in each dimension

                    
                        arr = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])                    newarr = arr.reshape(4, 3)
                
                

                    
                        arr = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])newarr = arr.reshape(2, 3, 2)
    
                

                    
                        arr = np.array([1, 2, 3, 4, 5, 6, 7, 8])print(arr.reshape(2, 4).base)
    
                

    Flattening array means converting a multidimensional array into a 1D array. We can use reshape(-1) to do this.

                    
                        arr = np.array([[1, 2, 3], [4, 5, 6]])newarr = arr.reshape(-1)
    
                
  13. Iterating Arrays
    Iterating Arrays
    Iterating Arrays
    Iterating means going through elements one by one.

    As we deal with multi-dimensional arrays in numpy, we can do this using basic for loop of python.

    If we iterate on a 1-D array it will go through each element one by one.

    Iterating Arrays Using nditer()
    The function nditer() is a helping function that can be used from very basic to very advanced iterations. It solves some basic issues which we face in iteration, lets go through it with examples.

    Iterating on Each Scalar Element
    In basic for loops, iterating through each scalar of an array we need to use n for loops which can be difficult to write for arrays with very high dimensionality. Iterating Array With Different Data Types

    We can use op_dtypes argument and pass it the expected datatype to change the datatype of elements while iterating.

    NumPy does not change the data type of the element in-place (where the element is in array) so it needs some other space to perform this action, that extra space is called buffer, and in order to enable it in nditer() we pass flags=['buffered'].


    Enumerated Iteration Using ndenumerate() Enumeration means mentioning sequence number of somethings one by one.

    Sometimes we require corresponding index of the element while iterating, the ndenumerate() method can be used for those usecases.

                
                    arr = np.array([1, 2, 3])                for x in arr:
                      print(x)
    
            

    If we iterate on a n-D array it will go through n-1th dimension one by one.

                
                    arr = np.array([[1, 2, 3], [4, 5, 6]])for x in arr:
      print(x)
    
            

    To return the actual values, the scalars, we have to iterate the arrays in each dimension.

                
                    arr = np.array([[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]])                for x in arr:
                      print(x)
    
            

    To return the actual values, the scalars, we have to iterate the arrays in each dimension.

                
                    arr = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])                for x in np.nditer(arr):
                      print(x)
    
            

                
                    arr = np.array([1, 2, 3])for x in np.nditer(arr, flags=['buffered'], op_dtypes=['S']):
      print(x)
    
            

                
                    arr = np.array([[1, 2, 3, 4], [5, 6, 7, 8]])for x in np.nditer(arr[:, ::2]):
      print(x)
    
            

                
                    arr = np.array([1, 2, 3])for idx, x in np.ndenumerate(arr):
      print(idx, x)
    
            
  14. NumPy Joining Array
    NumPy Joining Array
    Joining NumPy Arrays

    Joining means putting contents of two or more arrays in a single array.

    In SQL we join tables based on a key, whereas in NumPy we join arrays by axes.

    We pass a sequence of arrays that we want to join to the concatenate() function, along with the axis. If axis is not explicitly passed, it is taken as 0.


    Joining Arrays Using Stack Functions Stacking is same as concatenation, the only difference is that stacking is done along a new axis.

    We can concatenate two 1-D arrays along the second axis which would result in putting them one over the other, ie. stacking.

    We pass a sequence of arrays that we want to join to the stack() method along with the axis. If axis is not explicitly passed it is taken as 0.

                                            
                                                arr1 = np.array([1, 2, 3])
    arr2 = np.array([4, 5, 6])
    arr = np.concatenate((arr1, arr2))
                                        
                                        

                                        
                                            arr1 = np.array([[1, 2], [3, 4]])                                        arr2 = np.array([[5, 6], [7, 8]])
                                            
                                            arr = np.concatenate((arr1, arr2), axis=1)
                                    
                                    

                                            
                                                arr1 = np.array([1, 2, 3])                                            arr2 = np.array([4, 5, 6])
                                                
                                                arr = np.stack((arr1, arr2), axis=1) 
                                        
                                        

    NumPy provides a helper function: hstack() to stack along rows.

                                            
                                                arr1 = np.array([1, 2, 3])
                                                arr2 = np.array([4, 5, 6])
                                                arr = np.hstack((arr1, arr2))
                                        
                                        

                                            
                                                arr1 = np.array([1, 2, 3])                                            arr2 = np.array([4, 5, 6])
                                                
                                                arr = np.vstack((arr1, arr2))
                                        
                                        

    NumPy provides a helper function: dstack() to stack along height, which is the same as depth.

                                            
                                                arr1 = np.array([1, 2, 3])
    arr2 = np.array([4, 5, 6])
    arr = np.dstack((arr1, arr2))
                                        
                                        
  15. NumPy Splitting Array
    NumPy Splitting Array
    Splitting NumPy Arrays
    Splitting is reverse operation of Joining.

    Joining merges multiple arrays into one and Splitting breaks one array into multiple.

    We use array_split() for splitting arrays, we pass it the array we want to split and the number of splits.


    Splitting 2-D Arrays
    Use the same syntax when splitting 2-D arrays.

    Use the array_split() method, pass in the array you want to split and the number of splits you want to do.

                                            
                                                arr = np.array([1, 2, 3, 4, 5, 6])                                            newarr = np.array_split(arr, 3) 
                                        
                                        

                                        
                                            arr = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12], [13, 14, 15], [16, 17, 18]])                                        newarr = np.array_split(arr, 3, axis=1)
                                    
                                    

                                            
                                                arr = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12], [13, 14, 15], [16, 17, 18]])newarr = np.hsplit(arr, 3) 
                                        
                                        
  16. NumPy Searching Arrays
    NumPy Searching Arrays
    You have already seen the break statement used in an earlier chapter of this tutorial. It was used to "jump out" of a switch statement.

    The break statement can also be used to jump out of a loop.

    Searching Arrays
    You can search an array for a certain value, and return the indexes that get a match.

    To search an array, use the where() method.


    Search Sorted
    There is a method called searchsorted() which performs a binary search in the array, and returns the index where the specified value would be inserted to maintain the search order.

    The searchsorted() method is assumed to be used on sorted arrays.

    Searching Arrays

                        
                            arr = np.array([1, 2, 3, 4, 5, 4, 4])                        x = np.where(arr == 4)
                        
                        

    Search Sorted

                        
                            arr = np.array([6, 7, 8, 9])x = np.searchsorted(arr, 7,side='right')
                        
                        
                        
  17. NumPy Sorting Arrays
    NumPy Sorting Arrays
    Sorting Arrays
    Sorting means putting elements in an ordered sequence.

    Ordered sequence is any sequence that has an order corresponding to elements, like numeric or alphabetical, ascending or descending.

    The NumPy ndarray object has a function called sort(), that will sort a specified array.
    Note: This method returns a copy of the array, leaving the original array unchanged.

    Sort the array

                        
                            arr = np.array([3, 2, 0, 1])                        print(np.sort(arr))
                        
                        
  18. NumPy Filter Array
    NumPy Filter Array
    Filtering Arrays
    Getting some elements out of an existing array and creating a new array out of them is called filtering.

    In NumPy, you filter an array using a boolean index list.

    A boolean index list is a list of booleans corresponding to indexes in the array.

    If the value at an index is True that element is contained in the filtered array, if the value at that index is False that element is excluded from the filtered array.
    
        arr = np.array([41, 42, 43, 44])    # Create an empty list
        filter_arr = []
        
        # go through each element in arr
        for element in arr:
          # if the element is higher than 42, set the value to True, otherwise False:
          if element > 42:
            filter_arr.append(True)
          else:
            filter_arr.append(False)
        
        newarr = arr[filter_arr]
        
        print(filter_arr)
        print(newarr)
    
    
  19. Random Numbers in NumPy
    Random Numbers in NumPy
    What is a Random Number?
    Random number does NOT mean a different number every time. Random means something that can not be predicted logically.

    Pseudo Random and True Random.

    Computers work on programs, and programs are definitive set of instructions. So it means there must be some algorithm to generate a random number as well.

    If there is a program to generate random number it can be predicted, thus it is not truly random.

    Random numbers generated through a generation algorithm are called pseudo random. Can we make truly random numbers?

    Yes. In order to generate a truly random number on our computers we need to get the random data from some outside source. This outside source is generally our keystrokes, mouse movements, data on network etc.

    We do not need truly random numbers, unless its related to security (e.g. encryption keys) or the basis of application is the randomness (e.g. Digital roulette wheels).

    In this tutorial we will be using pseudo random numbers. Generate Random Number NumPy offers the random module to work with random numbers. Generate Random Array

    In NumPy we work with arrays, and you can use the two methods from the above examples to make random arrays.

    Integers The randint() method takes a size parameter where you can specify the shape of an array.

    Generate Random Number From Array The choice() method allows you to generate a random value based on an array of values. The choice() method takes an array as a parameter and randomly returns one of the values.

                                      
                                        from numpy import random
                              
                          

                                    
                                x = random.randint(100)
                            
                        

                                    
                                x = random.rand()                            print(x)
                            
                        

                                    
                                x = random.choice([3, 5, 7, 9])                            print(x)
                            
                        
  20. Random Data Distribution
    Random Data Distribution
    What is Data Distribution?
    Data Distribution is a list of all possible values, and how often each value occurs.

    Such lists are important when working with statistics and data science.

    The random module offer methods that returns randomly generated data distributions.


    Random Distribution
    A random distribution is a set of random numbers that follow a certain probability density function.

    Probability Density Function: A function that describes a continuous probability. i.e. probability of all values in an array.

    We can generate random numbers based on defined probabilities using the choice() method of the random module.

    The choice() method allows us to specify the probability for each value.

    The probability is set by a number between 0 and 1, where 0 means that the value will never occur and 1 means that the value will always occur.
                        
    x = random.choice([3, 5, 7, 9], p=[0.1, 0.3, 0.6, 0.0], size=(100))print(x)
                        
                        
  21. Random Permutations
    Random Permutations
    Random Permutations of Elements
    A permutation refers to an arrangement of elements. e.g. [3, 2, 1] is a permutation of [1, 2, 3] and vice-versa.

    The NumPy Random module provides two methods for this: shuffle() and permutation().

    Shuffling Arrays
    Shuffle means changing arrangement of elements in-place. i.e. in the array itself.

    The permutation() method returns a re-arranged array (and leaves the original array un-changed).

    The shuffle() method makes changes to the original array.

                        
                            arr = np.array([1, 2, 3, 4, 5])                        random.shuffle(arr)
                        
                        

    Generating Permutation of Arrays

                        
                            arr = np.array([1, 2, 3, 4, 5])print(random.permutation(arr))
                        
                        
  22. Normal (Gaussian) Distribution
    Normal (Gaussian) Distribution
    Normal Distribution
    The Normal Distribution is one of the most important distributions.

    It is also called the Gaussian Distribution after the German mathematician Carl Friedrich Gauss.

    It fits the probability distribution of many events, eg. IQ Scores, Heartbeat etc.

    Use the random.normal() method to get a Normal Data Distribution.
    It has three parameters:
    loc - (Mean) where the peak of the bell exists.
    scale - (Standard Deviation) how flat the graph distribution should be.
    size - The shape of the returned array.
                        
                            x = random.normal(size=(2, 3))                        print(x)
                        
                        
  23. Binomial Distribution
    Binomial Distribution
    Binomial Distribution
    Binomial Distribution is a Discrete Distribution.

    It describes the outcome of binary scenarios, e.g. toss of a coin, it will either be head or tails.

    It has three parameters:
    n - number of trials.
    p - probability of occurence of each trial (e.g. for toss of a coin 0.5 each).
    size - The shape of the returned array.


    Difference Between Normal and Binomial Distribution
    The main difference is that normal distribution is continous whereas binomial is discrete, but if there are enough data points it will be quite similar to normal distribution with certain loc and scale.

                        
                            x = random.binomial(n=10, p=0.5, size=10)print(x)
                        
                        
  24. Poisson Distribution
    Poisson Distribution
    Poisson Distribution
    Poisson Distribution is a Discrete Distribution.

    It estimates how many times an event can happen in a specified time. e.g. If someone eats twice a day what is probability he will eat thrice?

    It has two parameters:
    lam - rate or known number of occurences e.g. 2 for above problem.
    size - The shape of the returned array.


    Difference Between Normal and Poisson Distribution
    Normal distribution is continous whereas poisson is discrete.

    But we can see that similar to binomial for a large enough poisson distribution it will become similar to normal distribution with certain std dev and mean.


    Difference Between Poisson and Binomial Distribution
    The difference is very subtle it is that, binomial distribution is for discrete trials, whereas poisson distribution is for continuous trials.

    But for very large n and near-zero p binomial distribution is near identical to poisson distribution such that n * p is nearly equal to lam.


                        
                            x = random.poisson(lam=2, size=10)                        print(x)
                        
                        
  25. Uniform Distribution
    Uniform Distribution
    Uniform Distribution
    Used to describe probability where every event has equal chances of occuring.
    E.g. Generation of random numbers.
    It has three parameters:
    a - lower bound - default 0 .0.
    b - upper bound - default 1.0.
    size - The shape of the returned array.
                        
                            x = random.uniform(size=(2, 3))                        print(x)
                        
                        
  26. Logistic Distribution
    Logistic Distribution
    Logistic Distribution
    Logistic Distribution is used to describe growth.

    Used extensively in machine learning in logistic regression, neural networks etc.

    It has three parameters:
    loc - mean, where the peak is. Default 0.
    scale - standard deviation, the flatness of distribution. Default 1.
    size - The shape of the returned array.


    Difference Between Logistic and Normal Distribution
    Both distributions are near identical, but logistic distribution has more area under the tails, meaning it represents more possibility of occurrence of an event further away from mean.

    For higher value of scale (standard deviation) the normal and logistic distributions are near identical apart from the peak.
                        
                            x = random.logistic(loc=1, scale=2, size=(2, 3))                        print(x)
                        
                        
  27. Multinomial Distribution
    Multinomial Distribution
    Multinomial Distribution
    Multinomial distribution is a generalization of binomial distribution.

    It describes outcomes of multi-nomial scenarios unlike binomial where scenarios must be only one of two. e.g. Blood type of a population, dice roll outcome.

    It has three parameters:

    n - number of possible outcomes (e.g. 6 for dice roll).

    pvals - list of probabilties of outcomes (e.g. [1/6, 1/6, 1/6, 1/6, 1/6, 1/6] for dice roll).

    size - The shape of the returned array.

    Note: Multinomial samples will NOT produce a single value! They will produce one value for each pval.

    Note: As they are generalization of binomial distribution their visual representation and similarity of normal distribution is same as that of multiple binomial distributions.

                        
                            x = random.multinomial(n=6, pvals=[1/6, 1/6, 1/6, 1/6, 1/6, 1/6])                        print(x)
                        
                        
  28. Exponential Distribution
    Exponential Distribution
    Exponential Distribution
    Exponential distribution is used for describing time till next event e.g. failure/success etc.

    It has two parameters:

    scale - inverse of rate ( see lam in poisson distribution ) defaults to 1.0.

    size - The shape of the returned array.


    Relation Between Poisson and Exponential Distribution
    Poisson distribution deals with number of occurences of an event in a time period whereas exponential distribution deals with the time between these events.
                        
                            x = random.exponential(scale=2, size=(2, 3))print(x)
                        
                        
  29. Chi Square Distribution
    Chi Square Distribution
    Chi Square Distribution
    Chi Square distribution is used as a basis to verify the hypothesis.


    It has two parameters:

    df - (degree of freedom).

    size - The shape of the returned array.
                        
                            x = random.chisquare(df=2, size=(2, 3))                        print(x)
                        
                        
  30. Rayleigh Distribution
    Rayleigh Distribution
    Rayleigh Distribution
    Rayleigh distribution is used in signal processing.


    It has two parameters:

    scale - (standard deviation) decides how flat the distribution will be default 1.0).

    size - The shape of the returned array.


    Similarity Between Rayleigh and Chi Square Distribution
    At unit stddev and 2 degrees of freedom rayleigh and chi square represent the same distributions.
                        
                            x = random.rayleigh(scale=2, size=(2, 3))                        print(x)
                        
                        
  31. Pareto Distribution
    Pareto Distribution
    Pareto Distribution
    A distribution following Pareto's law i.e. 80-20 distribution (20% factors cause 80% outcome).


    It has two parameter:

    a - shape parameter.

    size - The shape of the returned array.
                
                    x = random.pareto(a=2, size=(2, 3))                print(x)
                
            
  32. Zipf Distribution
    Zipf Distribution
    Zipf distritutions are used to sample data based on zipf's law.


    Zipf's Law: In a collection, the nth common term is 1/n times of the most common term. E.g. the 5th most common word in English occurs nearly 1/5 times as often as the most common word.


    It has two parameters:

    a - distribution parameter.

    size - The shape of the returned array.
                
                    x = random.zipf(a=2, size=(2, 3))                print(x)
                
            
  33. NumPy ufuncs
    NumPy ufuncs
    What are ufuncs?
    ufuncs stands for "Universal Functions" and they are NumPy functions that operate on the ndarray object.


    Why use ufuncs? ufuncs are used to implement vectorization in NumPy which is way faster than iterating over elements.

    They also provide broadcasting and additional methods like reduce, accumulate etc. that are very helpful for computation.
    ufuncs also take additional arguments, like:
    where boolean array or condition defining where the operations should take place.

    dtype defining the return type of elements.
    out output array where the return value should be copied.


    What is Vectorization? Converting iterative statements into a vector based operation is called vectorization.

    It is faster as modern CPUs are optimized for such operations.
  34. Create Your Own ufunc
    Create Your Own ufunc
    How To Create Your Own ufunc
    To create your own ufunc, you have to define a function, like you do with normal functions in Python, then you add it to your NumPy ufunc library with the frompyfunc() method.


    The frompyfunc() method takes the following arguments:

    function - the name of the function.
    inputs - the number of input arguments (arrays).
    outputs - the number of output arrays.
    Check if a Function is a ufunc Check the type of a function to check if it is a ufunc or not. A ufunc should return <class 'numpy.ufunc'>
                        
                            def myadd(x, y):
                            return x+y
                          
                          myadd = np.frompyfunc(myadd, 2, 1)
                          
                          print(myadd([1, 2, 3, 4], [5, 6, 7, 8])
                        
                        

    Check if a Function is a ufunc

                            
                                print(type(np.add))
                            
                            
  35. Simple Arithmetic
    Simple Arithmetic
    You could use arithmetic operators + - * / directly between NumPy arrays, but this section discusses an extension of the same where we have functions that can take any array-like objects e.g. lists, tuples etc. and perform arithmetic conditionally.


    Arithmetic Conditionally: means that we can define conditions where the arithmetic operation should happen.


    All of the discussed arithmetic functions take a where parameter in which we can specify that condition.


    Addition
    The add() function sums the content of two arrays, and return the results in a new array.

    Subtraction
    The subtract() function subtracts the values from one array with the values from another array, and return the results in a new array. Multiplication

    The multiply() function multiplies the values from one array with the values from another array, and return the results in a new array. Division

    The divide() function divides the values from one array with the values from another array, and return the results in a new array. Power

    The power() function rises the values from the first array to the power of the values of the second array, and return the results in a new array

    Remainder
    Both the mod() and the remainder() functions return the remainder of the values in the first array corresponding to the values in the second array, and return the results in a new array

    Quotient and Mod
    The divmod() function return both the quotient and the the mod. The return value is two arrays, the first array contains the quotient and second array contains the mod.

    Absolute Values
    Both the absolute() and the abs() functions do the same absolute operation element-wise but we should use absolute() to avoid confusion with python's inbuilt math.abs()

                                        
                                            arr1 = np.array([10, 11, 12, 13, 14, 15])
                                            arr2 = np.array([20, 21, 22, 23, 24, 25])
                                            
                                            newarr = np.add(arr1, arr2)
                                            
                                            print(newarr)
                                        
                                        

                                        
                                            arr1 = np.array([10, 20, 30, 40, 50, 60])
                                            arr2 = np.array([20, 21, 22, 23, 24, 25])
                                            
                                            newarr = np.subtract(arr1, arr2)
                                            
                                            print(newarr)
                                        
                                        

                                        
                                            arr1 = np.array([10, 20, 30, 40, 50, 60])
                                            arr2 = np.array([20, 21, 22, 23, 24, 25])
                                            
                                            newarr = np.multiply(arr1, arr2)
                                            
                                            print(newarr)
                                            
                                        

                                        
                                            arr1 = np.array([10, 20, 30, 40, 50, 60])
                                            arr2 = np.array([3, 5, 10, 8, 2, 33])
                                            
                                            newarr = np.divide(arr1, arr2)
                                            
                                            print(newarr)
                                            
                                        

                                        
                                            arr1 = np.array([10, 20, 30, 40, 50, 60])
                                            arr2 = np.array([3, 5, 6, 8, 2, 33])
                                            
                                            newarr = np.power(arr1, arr2)
                                            
                                            print(newarr)
                                            
                                        

                                        
                                            arr1 = np.array([10, 20, 30, 40, 50, 60])
                                            arr2 = np.array([3, 7, 9, 8, 2, 33])
                                            
                                            newarr = np.mod(arr1, arr2)
                                            
                                            print(newarr)                                        
                                            
                                        
                                            
                                                arr1 = np.array([10, 20, 30, 40, 50, 60])
    arr2 = np.array([3, 7, 9, 8, 2, 33])newarr = np.remainder(arr1, arr2)print(newarr)
                                                
                                            

                                        
                                            arr1 = np.array([10, 20, 30, 40, 50, 60])
                                            arr2 = np.array([3, 7, 9, 8, 2, 33])
                                            
                                            newarr = np.divmod(arr1, arr2)
                                            
                                            print(newarr)
                                            
                                        

                                        
                                            arr = np.array([-1, -2, 1, 2, 3, -4])newarr = np.absolute(arr)print(newarr)
                                            
                                        
  36. Rounding Decimals
    Rounding Decimals
    Rounding Decimals
    There are primarily five ways of rounding off decimals in NumPy:

    1) truncation
    2) fix
    3) rounding
    4) floor
    5) ceil



    Truncation
    Remove the decimals, and return the float number closest to zero. Use the trunc() and fix() functions.


    Rounding
    The around() function increments preceding digit or decimal by 1 if >=5 else do nothing.

    E.g. round off to 1 decimal point, 3.16666 is 3.2


    Floor
    The floor() function rounds off decimal to nearest lower integer.

    E.g. floor of 3.166 is 3.


    Ceil
    The ceil() function rounds off decimal to nearest upper integer.

    E.g. ceil of 3.166 is 4.

                                        
                                            arr = np.trunc([-3.1666, 3.6667])
    
    print(arr)
                                        
                                        

                                        
                                            arr = np.fix([-3.1666, 3.6667])
    
    print(arr)
                                        
                                        

                                        
                                            arr = np.around(3.1666, 2)
    
    print(arr)
                                            
                                        

                                        
                                            arr = np.floor([-3.1666, 3.6667])
    
    print(arr)
                                            
                                        

                                        
                                            arr = np.ceil([-3.1666, 3.6667])
    
    print(arr)
                                            
                                        
  37. NumPy Logs
    NumPy Logs
    Logs
    NumPy provides functions to perform log at the base 2, e and 10.

    We will also explore how we can take log for any base by creating a custom ufunc.

    All of the log functions will place -inf or inf in the elements if the log can not be computed.


    Log at Base 2
    Use the log2() function to perform log at the base 2.


    Log at Any Base
    NumPy does not provide any function to take log at any base, so we can use the frompyfunc() function along with inbuilt function math.log() with two input parameters and one output parameter

    Note: The arange(1, 10) function returns an array with integers starting from 1 (included) to 10 (not included).

                                        
                                            arr = np.arange(1, 10)
    
    print(np.log2(arr))
                                        
                                        

                                        
                                            arr = np.arange(1, 10)
    
    print(np.log10(arr))
                                        
                                        

                                        
                                            arr = np.arange(1, 10)
    
    print(np.log(arr))
                                            
                                        

                                        
                                            from math import log
    import numpy as np
    
    nplog = np.frompyfunc(log, 2, 1)
    
    print(nplog(100, 15))
                                            
                                        
  38. NumPy Summations
    NumPy Summations
    Summations
    What is the difference between summation and addition?
    Addition is done between two arguments whereas summation happens over n elements.


    Summation Over an Axis
    If you specify axis=1, NumPy will sum the numbers in each array.


    Cummulative Sum
    Cummulative sum means partially adding the elements in array. E.g. The partial sum of [1, 2, 3, 4] would be [1, 1+2, 1+2+3, 1+2+3+4] = [1, 3, 6, 10]. Perfom partial sum with the cumsum() function.

                                        
                                            arr1 = np.array([1, 2, 3])
    arr2 = np.array([1, 2, 3])
    
    newarr = np.sum([arr1, arr2])
                                        
                                        

    If you specify axis=1, NumPy will sum the numbers in each array.

                                        
                                            arr1 = np.array([1, 2, 3])
                                            arr2 = np.array([1, 2, 3])
                                            
                                            newarr = np.sum([arr1, arr2], axis=1)
                                            
                                            print(newarr)
                                        
                                        

                                        
                                            arr = np.array([1, 2, 3])
    
                                            newarr = np.cumsum(arr)
                                            
                                            print(newarr)
                                            
                                        
  39. NumPy Products
    NumPy Products
    Products To find the product of the elements in an array, use the prod() function.


    Product Over an Axis
    If you specify axis=1, NumPy will return the product of each array.


    Cummulative Product
    Cummulative product means taking the product partially. E.g. The partial product of [1, 2, 3, 4] is [1, 1*2, 1*2*3, 1*2*3*4] = [1, 2, 6, 24] Perfom partial sum with the cumprod() function.

    Returns: 24 because 1*2*3*4 = 24

                                        
                                            arr = np.array([1, 2, 3, 4])
    
                                            x = np.prod(arr)
                                            
                                            print(x)
                                        
                                        

                                        
                                            arr1 = np.array([1, 2, 3, 4])
                                            arr2 = np.array([5, 6, 7, 8])
    
                                            newarr = np.prod([arr1, arr2], axis=1)
    
                                            print(newarr)
                                        
                                        

                                        
                                            arr = np.array([5, 6, 7, 8])
    
                                            newarr = np.cumprod(arr)
                                            
                                            print(newarr)
                                            
                                        
  40. NumPy Differences
    NumPy Differences
    Differences A discrete difference means subtracting two successive elements. E.g. for [1, 2, 3, 4], the discrete difference would be [2-1, 3-2, 4-3] = [1, 1, 1] To find the discrete difference, use the diff() function.
                                        
                                            arr = np.array([10, 15, 25, 5])
    
                                            newarr = np.diff(arr)
    
                                            print(newarr)
                                        
                                        
  41. LCM Lowest Common Multiple
    LCM Lowest Common Multiple
    Finding LCM (Lowest Common Multiple)
    The Lowest Common Multiple is the smallest number that is a common multiple of two numbers.

    Finding LCM in Arrays
    To find the Lowest Common Multiple of all values in an array, you can use the reduce() method.

    The reduce() method will use the ufunc, in this case the lcm() function, on each element, and reduce the array by one dimension.
                                        
                                            num1 = 4
                                            num2 = 6
                                            
                                            x = np.lcm(num1, num2)
                                            
                                            print(x)
                                        
                                        

    Finding LCM in Arrays

                                            
                                                arr = np.array([3, 6, 9])
    
                                                x = np.lcm.reduce(arr)
                                                
                                                print(x)
                                            
                                            
  42. GCD Greatest Common Denominator
    LCM Lowest Common Multiple
    Finding GCD (Greatest Common Denominator) The GCD (Greatest Common Denominator), also known as HCF (Highest Common Factor) is the biggest number that is a common factor of both of the numbers Finding GCD in Arrays To find the Highest Common Factor of all values in an array, you can use the reduce() method. The reduce() method will use the ufunc, in this case the gcd() function, on each element, and reduce the array by one dimension.
                                        
                                            num1 = 6
    num2 = 9
    
    x = np.gcd(num1, num2)
    
    print(x)
                                        
                                        

    Finding GCD in Arrays

                                            
                                                arr = np.array([20, 8, 32, 36, 16])
    
    x = np.gcd.reduce(arr)
    
    print(x)
                                            
                                            
  43. NumPy Trigonometric Functions
    NumPy Trigonometric Functions
    Trigonometric Functions
    NumPy provides the ufuncs sin(), cos() and tan() that take values in radians and produce the corresponding sin, cos and tan values.


    Convert Degrees Into Radians
    By default all of the trigonometric functions take radians as parameters but we can convert radians to degrees and vice versa as well in NumPy.

    Note: radians values are pi/180 * degree_values.


    Finding Angles Finding angles from values of sine, cos, tan. E.g. sin, cos and tan inverse (arcsin, arccos, arctan).

    NumPy provides ufuncs arcsin(), arccos() and arctan() that produce radian values for corresponding sin, cos and tan values given.


    Hypotenues
    Finding hypotenues using pythagoras theorem in NumPy.

    NumPy provides the hypot() function that takes the base and perpendicular values and produces hypotenues based on pythagoras theorem.

    Note: radians values are pi/180 * degree_values.

                                        
                                            arr = np.array([90, 180, 270, 360])
    
    x = np.deg2rad(arr)
    
    print(x)
                                        
                                        

                                        
                                            arr = np.array([np.pi/2, np.pi, 1.5*np.pi, 2*np.pi])
    
                                            x = np.rad2deg(arr)
                                            
                                            print(x)
                                        
                                        

                                        
                                            x = np.arcsin(1.0)
    
                                            print(x)
                                            
                                        

                                        
                                            arr = np.array([1, -1, 0.1])
    
    x = np.arcsin(arr)
    
    print(x)
                                            
                                        

                                        
                                            base = 3
    perp = 4
    
    x = np.hypot(base, perp)
    
    print(x)
                                            
                                        
  44. NumPy Hyperbolic Functions
    NumPy Hyperbolic Functions
    Hyperbolic Functions NumPy provides the ufuncs sinh(), cosh() and tanh() that take values in radians and produce the corresponding sinh, cosh and tanh values..


    Finding Angles Finding angles from values of hyperbolic sine, cos, tan. E.g. sinh, cosh and tanh inverse (arcsinh, arccosh, arctanh). Numpy provides ufuncs arcsinh(), arccosh() and arctanh() that produce radian values for corresponding sinh, cosh and tanh values given.

                                        
                                            ax = np.sinh(np.pi/2)
    
                                            print(x)
                                        
                                        

                                        
                                            x = np.arcsinh(1.0)
    
    print(x)
                                        
                                        

                                        
                                            arr = np.array([1, -1, 0.1])
    
    x = np.arcsinh(arr)
    
    print(x)
                                            
                                        
  45. NumPy Set Operations
    NumPy Set Operations
    What is a Set
    A set in mathematics is a collection of unique elements.

    Sets are used for operations involving frequent intersection, union and difference operations.


    Create Sets in NumPy
    We can use NumPy's unique() method to find unique elements from any array. E.g. create a set array, but remember that the set arrays should only be 1-D arrays.


    Finding Union
    To find the unique values of two arrays, use the union1d() method.


    Finding Intersection
    To find only the values that are present in both arrays, use the intersect1d() method.Note: the intersect1d() method takes an optional argument assume_unique, which if set to True can speed up computation. It should always be set to True when dealing with sets.


    Finding Difference
    To find only the values in the first set that is NOT present in the seconds set, use the setdiff1d() method.

    Note: the setdiff1d() method takes an optional argument assume_unique, which if set to True can speed up computation. It should always be set to True when dealing with sets.


    Finding Symmetric Difference
    To find only the values that are NOT present in BOTH sets, use the setxor1d() method. Note: the setxor1d() method takes an optional argument assume_unique, which if set to True can speed up computation. It should always be set to True when dealing with sets.

                                        
                                            arr = np.array([1, 1, 1, 2, 3, 4, 5, 5, 6, 7])
    
                                            x = np.unique(arr)
                                            
                                            print(x)
                                        
                                        

                                        
                                            arr1 = np.array([1, 2, 3, 4])
                                            arr2 = np.array([3, 4, 5, 6])
                                            
                                            newarr = np.union1d(arr1, arr2)
                                            
                                            print(newarr)
                                        
                                        

                                        
                                            arr1 = np.array([1, 2, 3, 4])
                                            arr2 = np.array([3, 4, 5, 6])
                                            
                                            newarr = np.intersect1d(arr1, arr2, assume_unique=True)
                                            
                                            print(newarr)
                                            
                                        

                                        
                                            set1 = np.array([1, 2, 3, 4])
                                            set2 = np.array([3, 4, 5, 6])
                                            
                                            newarr = np.setdiff1d(set1, set2, assume_unique=True)
                                            
                                            print(newarr)
                                            
                                        

                                        
                                            set1 = np.array([1, 2, 3, 4])
    set2 = np.array([3, 4, 5, 6])
    
    newarr = np.setxor1d(set1, set2, assume_unique=True)
    
    print(newarr)