<jupyter_start><jupyter_text>Introduction to Numpy  Why Bother Why would we bother with numpy? We already have python, which is a great language, that has support for numbers built in. We even have fancy things in our `math` module which comes as part of the base library. So why bother?In this notebook we'll just show one reason: performance. It really is a fast library specialized for numerical operations and we'll encounter many of those.   Lists, NumPy and Performance A downside of python is that because it is a dynamically-typed language (you don't have to specify the type of your variables while writing code, the interpreter will figure this out on the fly) we lose some performance. The `numpy` module is meant to counter this by giving us an API with python objects that call highly-performant C code so that we can still write python code for performant numerical problems. Let's do a small experiment and let's see if we can measure this. We can use the `%%timeit` notebook magic.<jupyter_code>import math
import numpy as np
%%timeit 
sum(range(100000))
%%timeit 
sum([i for i in range(100000)])
%%timeit
np.sum(np.arange(100000))
%%timeit
np.sum(np.arange(0,100000))
%%timeit
np.sum([i for i in range(100000)])
%%timeit
sum(np.arange(100000))
%%timeit 
s = 0 
for i in range(100000):
    s += 1<jupyter_output><empty_output><jupyter_text>First Observations 1. The code that we wrote in pure numpy (only `np.sum` and `np.arrange`) is orders of magnitude faster than it's python counterpart. 2. The performance seems to go away once we intermix the base python code and the numpy code. It seems much faster to keep numerical operations to the numpy side. Example; using the standard python `sum` function on a numpy array is the worst case! 3. All the speeds right now are pretty fast either way, but especially when we are dealing with large datasets we can expect a massive improvement.  Other Benchmarks Similar conclusions can be made when dealing with vectorized operations. Check these benchmarks.<jupyter_code>%%timeit 
[ 2 * i for i in range(100000) ]
%%timeit
np.arange(100000) * 2
%%timeit 
[ math.sqrt(i) for i in range(100000) ]
%%timeit 
np.sqrt( np.arange(100000) )<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.07101123595505618, 'fraction_numerical': 0.03325842696629214, 'mean_word_length': 4.827225130890052, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 9, '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': '2', 'dir': 'jupyter-structured-clean-dedup', 'id': '660632', 'license': 'MIT', 'n_tokens_mistral': 681, 'n_tokens_neox': 605, 'n_words': 328, 'path': 'notebooks/lecture/03.numpy/01-numpy-beats-lists.ipynb', 'repo_name': 'juls-dotcom/python_workshop', 'size': '2225'}