### Introduction

Now that we have introduced the fundamentals of Python, it's time to learn about NumPy and Pandas.

### NumPy

NumPy is the core library for scientific computing in Python. It provides a high-performance multidimensional array object, and tools for working with these arrays. It also has strong integration with Pandas, which is another powerful tool for manipulating financial data.

Python packages like NumPy and Pandas contain classes and methods which we can use by importing the package:

```
import numpy as np
```

##### Basic NumPy Arrays

A NumPy array is a grid of values, all of the same type, and is indexed by a tuple of nonnegative integers. Here we make an array by passing a list of Apple stock prices:

```
price_list = [143.73, 145.83, 143.68, 144.02, 143.5, 142.62]
price_array = np.array(price_list)
print(f'{price_array} {type(price_array)}')
[out]: [ 143.73 145.83 143.68 144.02 143.5 142.62]
<class 'numpy.ndarray'>
```

Notice that the type of array is "ndarray" which is a multi-dimensional array. If we pass np.array() a list of lists, it will create a 2-dimensional array.

```
Ar = np.array([[1,3], [2,4]])
print(f'{Ar} {type(Ar)}')
[out]: [[1 3]
[2 4]]
<class 'numpy.ndarray'>
```

We get the dimensions of an ndarray using the .shape attribute:

```
print(Ar.shape)
[out]: (2, 2)
```

If we create an 2-dimensional array (i.e. matrix), each row can be accessed by index:

```
print(Ar[0])
[out]: [1 3]
print(Ar[1])
[out]: [2 4]
```

If we want to access the matrix by column instead:

```
print(f'First column: {Ar[:,0]}')
[out]: First column: [1 2]
print(f'Second column: {Ar[:,1]}')
[out]: Second column: [3 4]
```

##### Array Functions

Some functions built in NumPy that allow us to perform calculations on arrays. For example, we can apply the natural logarithm to each element of an array:

```
np.log(price_array)
[out]: [4.96793654 4.98244156 4.9675886 4.96995218 4.96633504 4.96018375]
```

Other functions return a single value:

```
np.mean(price_array)
[out]: 143.896666667
print(np.std(price_array))
[out]: 0.967379047852
print(np.sum(price_array))
[out]: 863.38
print(np.max(price_array))
[out]: 145.83
```

The functions above return the mean, standard deviation, total and maximum value of an array.

### Pandas

Pandas is one of the most powerful tools for dealing with financial data. First we need to import Pandas:

```
import pandas as pd
```

##### Series

Series is a one-dimensional labeled array capable of holding any data type (integers, strings, float, Python object, etc.)

We create a Series by calling pd.Series(data), where data can be a dictionary, an array or just a scalar value.

```
price = [143.73, 145.83, 143.68, 144.02, 143.5, 142.62]
s = pd.Series(price)
print(s)
0 143.73
1 145.83
2 143.68
3 144.02
4 143.50
5 142.62
```

We can customize the indices of a new Series:

```
s = pd.Series(price, index = ['a', 'b', 'c', 'd', 'e', 'f'])
print(s)
a 143.73
b 145.83
c 143.68
d 144.02
e 143.50
f 142.62
```

Or we can change the indices of an existing Series:

```
s.index = [6,5,4,3,2,1]
print(s)
6 143.73
5 145.83
4 143.68
3 144.02
2 143.50
1 142.62
```

Series is like a list since it can be sliced by index:

```
print(s[1:])
print(s[:-2])
5 145.83
4 143.68
3 144.02
2 143.50
1 142.62
dtype: float64
6 143.73
5 145.83
4 143.68
3 144.02
dtype: float64
```

Series is also like a dictionary whose values can be set or fetched by index label:

```
print(s[4])
s[4] = 0
print(s)
143.68
6 143.73
5 145.83
4 0.00
3 144.02
2 143.50
1 142.62
dtype: float64
```

Series can also have a name attribute, which will be used when we make up a Pandas DataFrame using several series.

```
s = pd.Series(price, name = 'Apple Prices')
print(s)
print(s.name)
0 143.73
1 145.83
2 143.68
3 144.02
4 143.50
5 142.62
Name: Apple Prices, dtype: float64
Apple Prices
```

We can get the statistical summaries of a Series:

```
print(s.describe())
count 6.000000
mean 143.896667
std 1.059711
min 142.620000
25% 143.545000
50% 143.705000
75% 143.947500
max 145.830000
```

##### Time Index

Pandas has a built-in function specifically for creating date indices: pd.date_range(). We use it to create a new index for our Series:

```
time_index = pd.date_range('2017-01-01', periods = len(s), freq = 'D')
print(time_index)
s.index = time_index
print(s)
DatetimeIndex(['2017-01-01', '2017-01-02', '2017-01-03', '2017-01-04',
'2017-01-05', '2017-01-06'],
dtype='datetime64[ns]', freq='D')
2017-01-01 143.73
2017-01-02 145.83
2017-01-03 143.68
2017-01-04 144.02
2017-01-05 143.50
2017-01-06 142.62
Freq: D, Name: Apple Prices, dtype: float64
```

Series are usually accessed using the iloc[] and loc[] methods. iloc[] is used to access elements by integer index, and loc[] is used to access the index of the series.

iloc[] is necessary when the index of a series are integers, take our previous defined series as example:

```
s.index = [6,5,4,3,2,1]
print(s)
print(s[1])
6 143.73
5 145.83
4 143.68
3 144.02
2 143.50
1 142.62
Name: Apple Prices, dtype: float64
142.62
```

If we intended to take the second element of the series, we would make a mistake here, because the index are integers. In order to access to the element we want, we use iloc[] here:

```
print(s.iloc[1])
[out]: 145.83
```

While working with time series data, we often use time as the index. Pandas provides us with various methods to access the data by time index.

```
s.index = time_index
print(s['2017-01-03'])
[out]: 143.68
```

We can even access to a range of dates:

```
print(s['2017-01-02':'2017-01-05'])
2017-01-02 145.83
2017-01-03 143.68
2017-01-04 144.02
2017-01-05 143.50
Freq: D, Name: Apple Prices, dtype: float64
```

Series[] provides us a very flexible way to index data. We can add any condition in the square brackets:

```
print(s[s < np.mean(s)])
print(s[(s > np.mean(s)) & (s < np.mean(s) + 1.64*np.std(s))])
2017-01-01 143.73
2017-01-03 143.68
2017-01-05 143.50
2017-01-06 142.62
Name: Apple Prices, dtype: float64
2017-01-04 144.02
Freq: D, Name: Apple Price List, dtype: float64
```

As demonstrated, we can use logical operators like & (and), | (or) and ~ (not) to group multiple conditions.

### Summary

Here we have introduced NumPy and Pandas for scientific computing in Python. In the next chapter, we will dive into Pandas to learn resampling and manipulating Pandas DataFrame, which are commonly used in financial data analysis.