# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.14.4
#   kernelspec:
#     display_name: Python 3 (ipykernel)
#     language: python
#     name: python3
# ---

# + [markdown] jp-MarkdownHeadingCollapsed=true slideshow={"slide_type": "slide"} tags=[]
# # Arrays
#
# NumPy deals just perfect with arrays, because of
# - advanced overload of `__getitem__` operator for indexing, which is handy;
# - overload of other operators for comfortable shortcuts and intuitive interface;
# - methods and functions implemented in `C` language and powered by SIMD, which is fast;
# - rich library of functions and methods, which allows you to do almost whatever you want.

# + [markdown] slideshow={"slide_type": "slide"}
# # Creating arrays

# + jupyter={"outputs_hidden": false} slideshow={"slide_type": "fragment"}
from numpy import array, __version__
print('NumPy version of this slideshow is', __version__)
# -

arr = array([1, 2, 3])
print(arr)

# + [markdown] slideshow={"slide_type": "fragment"}
# You just have to provide `array` constructor of `numpy` module with `iterable` type.

# + [markdown] slideshow={"slide_type": "subslide"}
# More examples

# + jupyter={"outputs_hidden": false} slideshow={"slide_type": "-"}
ten = array(range(10))
matrix = array([[1,2], [3, 4]])
nested_matrix = array([matrix, matrix])
strange_array = array([[1], 2], dtype=object)

print('Range demo:', ten)
print('Matrix demo:', matrix)
print('Array of NumPy arrays:', nested_matrix)
print('Something strange:', strange_array)

# + [markdown] slideshow={"slide_type": "slide"}
# # Types
#
# NumPy can be fast, because it allows you to create arrays with elements of `C` language [types](http://docs.scipy.org/doc/numpy/user/basics.types.html)

# + [markdown] slideshow={"slide_type": "subslide"} tags=[]
# ## Shorthands
#
# Here you can see intuitive names of types to use
#
# | Data type  | Description |
# |------------|-------------|
# | bool       | Boolean (True or False) stored as a byte |
# | int        | Default integer type (same as C long; normally either int64 or int32) |
# | float      | Shorthand for float64. |
# | complex    | Shorthand for complex128. |

# + [markdown] slideshow={"slide_type": "subslide"}
# ## Integers
#
# If you need to use integers of specific sizes
# - use its size in bits as a suffix;
# - add `u` prefix to denote `unsigned` value.
#
# | Data type  | Description |
# |------------|-------------|
# | int8       | Byte (-128 to 127) |
# | int16      | Integer (-32768 to 32767) |
# | int32      | Integer (-2147483648 to 2147483647) |
# | int64      | Integer (-9223372036854775808 to 9223372036854775807) |
# | uint8      | Unsigned integer (0 to 255) |
# | uint16     | Unsigned integer (0 to 65535) |
# | uint32     | Unsigned integer (0 to 4294967295) |
# | uint64     | Unsigned integer (0 to 18446744073709551615) |

# + [markdown] slideshow={"slide_type": "subslide"}
# ## Floating point and complex
#
# There is [IEEE-754](https://en.wikipedia.org/wiki/IEEE_floating_point#IEEE_754-2008) standard for floating point arithmetics, which describes format of half (16 bits), single (32 bits), double (64 bits), quadruple (128 bits) and octuple (256 bits) numbers
#
# Standard `C` has single precision `float`, double precision `double` and additional `long double` which is at least as accurate as regular `double`
#
# | Data type  | Description |
# |------------|-------------|
# | float16    | Half precision float: sign bit, 5 bits exponent, 10 bits mantissa |
# | float32    | Single precision float: sign bit, 8 bits exponent, 23 bits mantissa |
# | float64    | Double precision float: sign bit, 11 bits 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) |

# + [markdown] slideshow={"slide_type": "slide"}
# # Specify data type
#
# NumPy `array` has a string `dtype` property to store and specify data type

# + jupyter={"outputs_hidden": false}
int_array = array([1., 2.5, -0.7], dtype=int)
print(f'You have {int_array} array of type {int_array.dtype}')

# + [markdown] slideshow={"slide_type": "fragment"}
# Note that typecast was made automatically

# + [markdown] slideshow={"slide_type": "subslide"}
# NumPy will not allow you to create wrong array with specific type

# + jupyter={"outputs_hidden": false}
try:
    array([[0], 1], dtype=int)
except ValueError as e:
    print(e)

# + [markdown] slideshow={"slide_type": "subslide"}
# NumPy assigned data type automatically, if it was not specified

# + jupyter={"outputs_hidden": false}
arrays = [
    array([1, 2, 3]),
    array(((1, 2), (3, 4.))),
    array('Hello world')
]
for a in arrays:
    print(f'{a.dtype}: {a}')

# + [markdown] slideshow={"slide_type": "slide"}
# # Operations on arrays
#
# You can simply apply elementwise operations

# + jupyter={"outputs_hidden": false}
LENGTH = 4
a, b = array(range(LENGTH)), array(range(LENGTH, LENGTH*2))
print('Arithmetic')
print(f'{a} +  {b} = {a + b}')
print(f'{a} *  {b} = {a * b}')
print(f'{a} ** {b} = {a ** b}')
print(f'{a} /  {b} = {a / b}')
print('Binary')
print(f'{a} ^  {b} = {a ^ b}')
print(f'{a} |  {b} = {a | b}')
print(f'{a} &  {b} = {a & b}')

# + [markdown] slideshow={"slide_type": "slide"}
# # Indexing
#
# Indexing of NumPy arrays is very agile
#
# Just look on entitites which can be used as an index
# - boolean arrays;
# - integer arrays;
# - numbers;
# - Ellipsis;
# - tuples of them;
# - etc.

# + [markdown] slideshow={"slide_type": "subslide"}
# ## Integers array
#
# You can get values from array by `iterable` (but not tuples) of indices

# + jupyter={"outputs_hidden": false}
arr = array(range(10))
indices_list = [
    [1, 5, 8],
    range(1, 6, 2),
    array([8, 0, -1], dtype='int8')
]

for indices in indices_list:
    print(f'Indexed by {str(indices):<14}: {arr[indices]}')

# + [markdown] slideshow={"slide_type": "subslide"}
# ## Boolean array
#
# Boolean arrays can be result of comparison and syntax of its usage is very handy

# + jupyter={"outputs_hidden": false}
arr = array(range(5))

print('Mask for items greater than 2:', arr > 2)
print('Items greater than 2:         ', arr[arr>2])

# + [markdown] slideshow={"slide_type": "fragment"}
# This can be read as "Give me the numbers which are greater than two"

# + [markdown] slideshow={"slide_type": "subslide"}
# What you actually asked for
# - get elementwise comparison of array with scalar
# - provide me with array of results
# - fetch elements from the array, which are correspond to `True`
#
# This means that you can use another array to get values from this one

# + jupyter={"outputs_hidden": false}
a, b = array(range(0, 5)), array(range(5, 10))

print(a[b>7])
# -

# This gives you elements from array `a`, corresponding elements from `b` of which are greater than `7`

# + [markdown] slideshow={"slide_type": "subslide"}
# ## Tuple
#
# Tuples are used to access `n`-dimensional array elements

# + jupyter={"outputs_hidden": false}
matrix = array([range(3*i, 3*(i+1)) for i in range(3)])

print('We have a matrix of shape', matrix.shape)
print('Regular Python indexing   ', matrix[0][2])
print('Implicit tuple declaration', matrix[0, 2])
print('Explicit tuple declaration', matrix[(0, 2)])

# + [markdown] slideshow={"slide_type": "subslide"}
# It was noted that we can use "tuple of them"
#
# This means that it can contain not only numbers but arrays and slices

# + jupyter={"outputs_hidden": false}
print('All elements of the first column', matrix[:, 0])
print('Get elements of the second column', matrix[:, 1])
print('Pick first and last column', matrix[:, 0:3:2])
print('Get only first row', matrix[0, :])
print('You could do this easier but nevermind', matrix[0])
print('Get first two elements of the third column', matrix[0:2, 2])

# + [markdown] slideshow={"slide_type": "subslide"}
# ## Ellipsis
#
# `Ellipsis` is a type for `ellipsis` constant written as `...` (three dots)
#
# It's very handy to be used in your own iterable types indexing when you want to skip some entries

# + [markdown] slideshow={"slide_type": "subslide"}
# The following example usage of `ellipsis` behaves just like fetch of all elements

# + jupyter={"outputs_hidden": false}
a = array(range(5))

print(a)
print(a[:])
print(a[...])

# + [markdown] slideshow={"slide_type": "subslide"} tags=[]
# Though it's useful for `n`-dimensional arrays when you want to skip multiple dimensions

# + jupyter={"outputs_hidden": false}
array3d = array([[range(3*(i+j), 3*(i+j+1)) for i in range(3)] for j in range(3)])
print(f'Here is array of shape {array3d.shape}: {array3d}')

# + [markdown] slideshow={"slide_type": "subslide"}
# Element of this array is a matrix, element of which is an array

# + jupyter={"outputs_hidden": false}
print(f'Item is a matrix of shape {array3d[0].shape}: {array3d[0]}')
print(f'Item of the matrix is an array of shape {array3d[0][0].shape}: {array3d[0][0]}')
print(f'Don`t forget about tuples: {array3d[0, 0]}')

# + [markdown] slideshow={"slide_type": "subslide"}
# If you want to get only last elements of each row in this huge thing you can do following

# + jupyter={"outputs_hidden": false}
array3d[:, :, -1]

# + [markdown] slideshow={"slide_type": "fragment"}
# Also you can avoid these slices and use `ellipsis`

# + jupyter={"outputs_hidden": false}
array3d[..., -1]

# + [markdown] slideshow={"slide_type": "subslide"}
# `Ellipsis` can be placed in the middle or in the end
#
# It will mean that you fetch all elements from not specified dimensions

# + jupyter={"outputs_hidden": false}
print('First matrix with all elements', array3d[0, ...])
print('First elements of all rows of the second matrix', array3d[1, ..., 0])

# + [markdown] slideshow={"slide_type": "slide"} tags=[]
# # Advanced indexing

# + slideshow={"slide_type": "fragment"} tags=[]
from numpy import newaxis
from numpy.random import random
from numpy.testing import assert_allclose
from time import time

# + [markdown] slideshow={"slide_type": "subslide"} tags=[]
# [numpy.newaxis](https://numpy.org/devdocs/reference/constants.html#numpy.newaxis) can be replaced by `None` and is used to create an iterator of the array but with additional dimensionality.
#
# You don't need to use [numpy.reshape](https://numpy.org/doc/stable/reference/generated/numpy.reshape.html) or
# [numpy.expand_dims](https://numpy.org/doc/stable/reference/generated/numpy.expand_dims.html) when you only need to perform some actions on the same array but with dimensionality.

# + slideshow={"slide_type": "fragment"} tags=[]
print("Column-vector")
print(array([0, 1])[:, None])
print("Row-vector", array([0, 1])[newaxis, :])

# + [markdown] slideshow={"slide_type": "subslide"} tags=[]
# ## Outer product
#
# Outer product $u \otimes v$ of two vectors $u$ and $v$ is a matrix $M$
# with elements $M_{ij} = u_i \cdot v_j$.
#
# You can use `numpy.outer` or you can use advanced indexing techniques!

# + slideshow={"slide_type": "fragment"} tags=[]
from numpy import outer

a = random(10_000)
b = random(10_000)

start = time()
c = outer(a, b)
print(f"`numpy.outer` took   {time() - start:>.2}s")

start = time()
d = a[:, newaxis] * b[newaxis, :]
print(f"`array.__mul__` took {time() - start:>.2}s")

del a
del b

assert_allclose(c, d)

del c
del d

StarcoderdataJupyterScriptsDedupFiltered