<jupyter_start><jupyter_text>Numpy is a library that provides support for high performance numeric operations on arrays. Although it supports multidimensional arrays, today we're just going to look at one-dimensional arrays, which are like lists.<jupyter_code># to use numpy, import it
import numpy as np

# numpy provides an array type
# create an array
fib = [1, 1, 2, 3, 5, 8, 13]
a = np.array(fib)
a<jupyter_output><empty_output><jupyter_text>Unlike lists, all elements of an array are of the same type. This is called the array's "dtype":<jupyter_code># arrays have data types called "dtypes"
# all elements of the array must have the same dtype
a.dtype
# when constructing an array, numpy
# tries to make all the values the same dtype
np.array([1, 'joe', False])<jupyter_output><empty_output><jupyter_text>Arrays can be accessed by indexes or slices, just like lists<jupyter_code># index
a[2]
# slice
a[2:7]<jupyter_output><empty_output><jupyter_text>Importantly, arrays support bulk operations which are performed element-wise. This is called vectorization.<jupyter_code>-1 * a
a + a<jupyter_output><empty_output><jupyter_text>Arrays also support vectorized aggregation functions.<jupyter_code>a.mean()
a.max()
a.argmax()<jupyter_output><empty_output><jupyter_text>Numpy provides many ways to initialize arrays.<jupyter_code>np.ones(shape=10)
r = np.random.normal(size=10)
r<jupyter_output><empty_output><jupyter_text>Numpy adds the ability to handle missing values, using `np.nan`<jupyter_code>r[2] = np.nan
r
# NaNs propagate across operations
r + 10
# important limitation: integers cannot be NaNs
my_array = np.array([1, 2, 3, 4, 5], dtype='int')
my_array
try:
    my_array[2] = np.nan
except ValueError as e:
    print(e)
# to fix this in numpy you must convert your array to a different dtype
maf = my_array.astype('float')
maf[2] = np.nan
maf<jupyter_output><empty_output><jupyter_text>An important thing about arrays is that performing operations on them usually does not copy the underlying data. This can cause subtle effects.<jupyter_code>a = np.array([1, 2, 3, 4, 5])
a
# this operation does not copy the data
b = a
b
# if you modify a's data, b will reflect that
# because it is backed by the same array
a[0] = 100
a

b
# if you don't want this behavior, use copy
a = np.array([1, 2, 3, 4, 5])
b = a.copy()
a[0] = 100
b<jupyter_output><empty_output><jupyter_text>When using Pandas, this aspect of numpy can be confusing. Fortunately the Pandas documentation is very explicit about when it will and won't copy data. numpy provides a large number of vectorized operations on arrays.<jupyter_code># generate a range of values
coords = np.arange(-10, 11)
coords
# perform an operation on a range of values
coords = np.arange(-20, 20, 0.1)
coords[:5]
# compute an expression over the array
sinc = np.sin(coords) / coords

%matplotlib inline
from matplotlib import pyplot as plt

plt.plot(sinc);
# example: create some random data
# this example uses
# - libraries (matplotlib, numpy)
# - arrays
# - vectorized operations
# - function nesting
# - keyword arguments
plt.plot(np.cumsum(np.random.normal(size=100)));
# numpy arrays can contain strings

names = np.array(['John','Paul','George','Ringo'])
names
# example: random permutation
np.random.shuffle(names)
names
# indexing is still positional
names[0]<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.09907490301402566, 'fraction_numerical': 0.017905102954341987, 'mean_word_length': 5.433781190019194, 'pattern_counts': {'":': 1, '<': 37, '<?xml version=': 0, '>': 37, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '9', 'dir': 'jupyter-structured-clean-dedup', 'id': '216430', 'license': 'MIT', 'n_tokens_mistral': 1142, 'n_tokens_neox': 1082, 'n_words': 404, 'path': '02 arrays with numpy.ipynb', 'repo_name': 'WHOIGit/pandas-talk', 'size': '3351'}