<jupyter_start><jupyter_text>![Astrofisica Computacional](../logo.png) --- 01. Introduction to `NumPy`<NAME> (<EMAIL>)---  About this notebookIn this notebook we present an introduction to the `numpy` package. ---<jupyter_code>import numpy as np
import sys

print(f"Python version:  {sys.version}\n"
      f"NumPy version:   {np.__version__}")<jupyter_output>Python version:  3.9.5 | packaged by conda-forge | (default, Jun 19 2021, 00:24:55) 
[Clang 11.1.0 ]
NumPy version:   1.20.3<jupyter_text>Arrays in NumPy: `ndarray` A 1-dimensional array<jupyter_code>a = np.array([1., 2., 3., 4., 5.])
a
type(a)<jupyter_output><empty_output><jupyter_text>Properties of an array<jupyter_code>a.size  # Gives the number of elements in the array
a.ndim  # Number of dimensions of the array
a.shape  # Shape of the array
a.dtype  # Type of the elements in the array<jupyter_output><empty_output><jupyter_text>Multi-dimensional Arrays<jupyter_code>b = np.array([[1., 2., 3.],[4., 5., 6.], [7., 8., 9.]])
b
print(type(b))
print(b.size)
print(b.ndim)
print(b.shape)
print(b.dtype)<jupyter_output><class 'numpy.ndarray'>
9
2
(3, 3)
float64<jupyter_text>Array Methods<jupyter_code>a<jupyter_output><empty_output><jupyter_text>Minimum, maximum and mean<jupyter_code>a.min(), a.max(), a.mean()<jupyter_output><empty_output><jupyter_text>Sum of elements<jupyter_code>b
b.sum()
b.sum(axis=0)
b.sum(axis=1)
b
b.T # Transpose of b<jupyter_output><empty_output><jupyter_text>Operations with Arrays<jupyter_code>a
a - 5.
a*np.pi/2.<jupyter_output><empty_output><jupyter_text>$a^e$<jupyter_code>a**np.e<jupyter_output><empty_output><jupyter_text>$e^a$<jupyter_code>np.e**a<jupyter_output><empty_output><jupyter_text>Products of arrays<jupyter_code>a * a  # Element-wise product
a @ a  # Matrix Product
np.dot(a,a) # Equivalent form of the matrix product<jupyter_output><empty_output><jupyter_text>Other operations<jupyter_code>a
a < 3
a == 4<jupyter_output><empty_output><jupyter_text>Other operations<jupyter_code>(a > 3) & (a < 5)  # bitwise AND
a < np.array([2, 3, 5, 2, 1])
np.sum(a > 2)<jupyter_output><empty_output><jupyter_text>Indices and Slicing<jupyter_code>a[0]  # Remember that indices start at 0
a[-1]  # Index [-1] refers to the last element
a[2:5] # Indices [start:end]
a[2:6:2]  # Indices [start:end:step]
a[::-1]  # Reverse an array<jupyter_output><empty_output><jupyter_text>Indixing and Slicing in Multiple Dimensions<jupyter_code>b
b[1]
b[0, 2]
b[:,1] # The symbol `:` selects the whole axis
b[0, 1:4]
b[:, 1:4]  
b[:, 0:5:2]
b[::-1]  # reverses axis 0
b[::-1, ::-1]  # reverses both axes<jupyter_output><empty_output><jupyter_text>Advanced Indexing<jupyter_code>d = np.array([10, 15, 2., 5, 10, 2, 1])
d
mask = np.array([True, False, False, True, False, True, True])
mask
d[mask]
d[[1, 3, 1, 6]]<jupyter_output><empty_output><jupyter_text>Helper Functions to Create Arrays<jupyter_code>np.arange(7) # Creates array from a range of integers
np.arange(2,7)
np.arange(2,7,2)
np.linspace(1, 5, 10) #[start, end, number of points in the interval]
np.ones(10)
np.ones(10, dtype='i') # Creates an array with ones of the integer type
np.ones(10, dtype='i2')
np.ones([3,3])
np.zeros(5)
np.zeros(5, dtype='i')
np.zeros([2, 4])
np.empty(20) # Empty array 
np.eye(4) # n-dimensional identity array 
b
np.ones_like(b) # Array with the shape of array b<jupyter_output><empty_output><jupyter_text>Random numbers<jupyter_code>rng = np.random.default_rng(413)  # Starts the random number generator with seed=413
rng.random((3, 4)) # random number array between 0 and 1  with (shape)
rng.integers(1, 10, (3, 10)) # (min, max, shape)
rng.uniform(0, 5, (2,5)) # Samples are uniformly distributed over the interval (min, max, shape)<jupyter_output><empty_output><jupyter_text>Changing the shape of an array<jupyter_code>c = np.arange(16)
c
c.reshape(4, 4)
g = np.array([0,1,2,3])
g
g.shape
newg = g[:, np.newaxis]  # inserts a new axis, making it two dimensional
newg
newg.shape<jupyter_output><empty_output><jupyter_text>Flatten an array<jupyter_code>b
b.ravel()<jupyter_output><empty_output><jupyter_text>Universal Functions (`ufunc`)A `ufunc` is a **vectorized wrapper** for a function that takes as inputs the components of an array and returns an equalnumber of outputs.- Math operations (`add()`, `subtract()`, `square()`, `log10()`, ...)- Trigonometric functions (`sin()`, `cos()`, `tan()`, `deg2rad()`, ...)- Comparison functions (`greater()`, `less_equal()`, `fmax()`, ...) The universal functions all are subclasses of `np.ufunc`<jupyter_code>type(np.cos)  # they all are subclasses of np.ufunc
b
np.sqrt(b)
np.cos(b)<jupyter_output><empty_output><jupyter_text>Creating your own `ufunc` It is possible to create a new `ufunc` using `np.frompyfunc(func, nin, nout)`<jupyter_code>x = rng.integers(0, 100, 10)
x
def f(x):
    return 1 if x > 23 else 0
f(x) # This will raise an error of interpretation
uf = np.frompyfunc(f, 1, 1)
uf(x)
f(5)<jupyter_output><empty_output><jupyter_text>Copies of an array<jupyter_code>original = np.arange(10)
original
ref_to_original = original # The new object will point to `original`
ref_to_original
ref_to_original[2] = 99

original   # changing `ref_to_original` has changed `original` !!!!
original = np.arange(10)

copy_of_original = np.copy(original) # Create a copy of the array
copy_of_original
copy_of_original[2] = 99

copy_of_original, original   # changing `copy_of_original` doesn't change `original` !!!!
original = np.arange(10)

single_value = original[5]  # single element access returns a copy
single_value
single_value = 9999
original             # not affected when `single_value` is changed<jupyter_output><empty_output><jupyter_text>Slices<jupyter_code>original = np.arange(10)

a_slice = original[2:4]    # slice of original
a_slice
a_slice[1] = 1000  # changing elements of `a_slice` changes to `original`
original
original[3:6] = [101, 102, 103]   # changing 'original' also changes 'slice'
a_slice<jupyter_output><empty_output><jupyter_text>Speed Comparisons<jupyter_code>def my_mean(numbers):
    s = 0
    N = len(numbers)
    for i in range(N):
        s += numbers[i]
    return s/N

numbers = np.arange(1000000)
%timeit my_mean(numbers)
def my_mean(numbers):
    return sum(numbers)/len(numbers)

numbers = list(range(1000000))
%timeit my_mean(numbers) # Defined function
numbers = np.arange(1000000)
%timeit np.mean(numbers) # Numpy function
import numba as nb

@nb.jit
def my_mean(numbers):
    s = 0
    N = len(numbers)
    for i in range(N):
        s += numbers[i]
    return s/N

numbers = np.arange(1000000)
%timeit my_mean(numbers)<jupyter_output>159 µs ± 2.49 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)<jupyter_text>Speed Comparisons<jupyter_code>def my_sum(N):
    s = 0
    for i in range(N):
        s +=i
    return s

%timeit my_sum(100000000)
numbers_to_sum = np.arange(100000000)
%timeit np.sum(numbers_to_sum)
@nb.jit
def my_sum(N):
    s = 0
    for i in range(N):
        s +=i
    return s

%timeit my_sum(100000000)<jupyter_output>70.2 ns ± 0.242 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
{'fraction_non_alphanumeric': 0.14463101453365318, 'fraction_numerical': 0.04091999435586285, 'mean_word_length': 5.431941923774954, 'pattern_counts': {'":': 0, '<': 107, '<?xml version=': 0, '>': 107, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 4, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '26', 'dir': 'jupyter-structured-clean-dedup', 'id': '68522', 'license': 'MIT', 'n_tokens_mistral': 3000, 'n_tokens_neox': 2770, 'n_words': 689, 'path': '01. Numpy/01. Introduction to NumPy.ipynb', 'repo_name': 'ashcat2005/ComputationalAstrophysics2021', 'size': '7113'}