# BÁO CÁO ĐỒ ÁN 01: REGRESSION

__TÊN MÔN HỌC:__ NHẬP MÔN HỌC MÁY

__ĐỀ TÀI:__ CHI PHÍ SỬ DỤNG DỊCH VỤ Y TẾ

__GIẢNG VIÊN:__ NGUYỄN TIẾN HUY

__THỨ TỰ NHÓM:__ 07

__THÀNH VIÊN:__

- 18120184 Nguyễn Nguyên Khang
- 18120189 Trần Đăng Khoa
- 18120264 Nguyễn Duy Vũ
- 18120283 Nguyễn Chiêu Bản
- 18120286 Nguyễn Quốc Bảo

__PHÂN CÔNG:__

Công việc | Thực hiện | Mức độ hoàn thành
------------ | ------------- | ------------
Khám phá dữ liệu cơ bản | Vũ | 100%
Tiền xử lý dữ liệu | Vũ | 100%
Mô hình hóa dữ liệu | Bản, Bảo | 100%
Phân tích dữ liệu tìm Insight| Khang, Khoa | 100%

## Mục Lục

- [I. Phân tích dữ liệu](#I.-Phân-tích-dữ-liệu)
    - [1. Vẽ biểu đồ một biến và nhận xét](#1.-Vẽ-biểu-đồ-một-biến-và-nhận-xét)
    - [2. Vẽ biểu đồ các biến tương quan và nhận xét](#2.-Vẽ-biểu-đồ-các-biến-tương-quan-và-nhận-xét)
    - [3. VIF](#3.-VIF)
    - [4. Insight: Sex có ảnh hưởng đến Smoker?](#4.-Insight:-Sex-có-ảnh-hưởng-đến-Smoker?)
    - [5. Insight: Trung bình của 'age', 'bmi', 'children' giữa người có hút thuốc và người không hút thuốc có bằng nhau](#5.-Insight:-Trung-bình-của-'age',-'bmi',-'children'-giữa-người-có-hút-thuốc-và-người-không-hút-thuốc-có-bằng-nhau)
    - [6. Insight: Sự phụ thuộc của 'charges' vào 'sex', 'smoker', 'age', 'bmi', 'children'](#6.-Insight:-Sự-phụ-thuộc-của-'charges'-vào-'sex',-'smoker',-'age',-'bmi',-'children')
- [II. Thuật toán sử dụng](#II.-Thuật-toán-sử-dụng)
    - [1. Cách thức đánh giá mô hình](#1.-Cách-thức-đánh-giá-mô-hình)
    - [ 2. Thuật toán SVR](#2.-Thuật-toán-SVR)
        - [a. Giới thiệu SVR](#a.-Giới-thiệu-SVR)
        - [b. Sử dụng SVR từ thư viện Scikit-learn](#b.-Sử-dụng-SVR-từ-thư-viện-Scikit-learn)
        - [c. Thử nghiệm tương tự với các kernel khác](#c.-Thử-nghiệm-tương-tự-với-các-kernel-khác)
        - [d. Thử xóa các outlier](#d.-Thử-xóa-các-outlier)
    - [3. Dùng Simple Linear Regression từ thư viện Scikit-learn](#3.-Dùng-Simple-Linear-Regression-từ-thư-viện-Scikit-learn)
        - [Trực quan hóa mô hình](#Trực-quan-hóa-mô-hình)
- [III. Tham khảo](#III.-Tham-khảo)

## I. Phân tích dữ liệu

Thông tin về dataset:

Tên cột | Ý nghĩa
------------ | -------------
Age| Tuổi
Sex| Giới tính
BMI| Chỉ số khối cơ thể
Children| Số lượng trẻ con/người phụ thuộc
Smoker| Tình trạng hút thuốc
Region| Khu vực sinh sống
Charges| Chi phí y tế cá nhân

### 1. Vẽ biểu đồ một biến và nhận xét

```python
Biến charges:
```

Nhận xét: Biến charges có phân bố bị lệch trái, nhiều outlier

```python
Biến age:
```

Nhận xét: Biến age có phân bố chuẩn

```python
Biến bmi:
```

Nhận xét: Biến bmi có phân bố chuẩn, tồn tại outlier

```python
Biến sex:
```

Nhận xét: Tỉ lệ nam nữ bằng nhau

```python
Biến smoker:
```

Nhận xét: Tỉ lệ người không hút thuốc gấp 4 lần người hút thuốc

```python
Biến children:
```

Nhận xét: Tỉ lệ người có càng nhiều con giảm dần

### 2. Vẽ biểu đồ các biến tương quan và nhận xét

Trước tiên, ta tính ma trận tương quan

```python

```

Có thể thấy những thuộc tính như age (yếu), bmi (yếu), smoker (mạnh) có tương quan với thuộc tính charges

```python
Biểu đồ thể hiện sự mất tiền vào chi phí y tế của người có hút thuốc
```

Biểu đồ trên cho ta thấy người hút thuốc thì có chi phí y tế cao hơn, cụ thể :
- hơn 75% người hút thuốc trả chi phí cao hơn hầu hết tất cả người không hút thuốc
- chi phí thấp nhất của người hút thuốc chỉ nhỉnh hơn một chút so với chi phí của 75% người không hút thuốc.
- nếu chi phí dưới 10k, xác suất cao là người đó không hút thuốc
- nếu chi phí trên 20k, xác suất cao là người đó hút thuốc

```python
Phân bố của chi phí y tế theo độ tuổi
```

Nhìn vào biểu đồ trên, ta thấy
- người càng cao tuổi thì số tiền chi cho y tế càng nhiều
- Nếu dưới 35 tuổi và không hút thuốc thì khả năng cao chi phí dưới 6k

```python
Phân bố của chi phí y tế theo bmi
```

- Người hút thuốc và có chỉ số BMI lớn hơn 30 thì chi phí tổi thiểu là khoảng 30k

### 3. VIF

```python
Bảng VIF
```

        feature       VIF
    0       sex  1.966855
    1    smoker  1.254563
    2       age  7.658193
    3       bmi  8.638958
    4  children  1.816248

1 = Không tương quan [1]

Giữa 1 và 5 = Tương quan vừa [1]

Lớn hơn 5 = Tương quan mạnh [1]

Ta thấy các biến `sex`, `smoker`, `children` tương quan vừa với các biến còn lại.

`age` và `bmi` có sự tương quan mạnh với các biến còn lại

Nên thu thập thêm data để giảm sự phụ thuộc giữa các biến

### 4. Insight: Sex có ảnh hưởng đến Smoker?

${H_0}$: sex và smoker độc lập nhau

${H_A}$: sex và smoker phụ thuộc nhau

Đặt:

${A =}$  sex, ${A_1 =}$ `male`, ${A_2}$ = `female`

${B =}$ smoker, ${B_1 =}$ `yes`, ${B_2 =}$ `no`

Ta có:

${H_0}$: ${P(A_i\cap B_j) = P(A_i)P(B_j)}$

${H_A}$: ${P(A_i\cap B_j) \neq P(A_i)P(B_j)}$

Phần dưới sẽ trình bày về mặt toán học lẫn sử dụng thư viện scipy.stats để tính toán

```python
contigency
```

      no
      yes
      Pr(Ai)

      sex

      female
      406
      91
      0.495513

      male
      391
      115
      0.504487

Ta đã tính được ${Pr(A_i)}$ như bảng trên và

${Pr(B_1)}$ = 0.2053838484546361

${Pr(B_2)}$ =  0.7946161515453639

---

Đến đây ta có thể tính:

Giá trị mong đợi ${E}$:

\begin{equation}
\text{Do kỳ vọng A và B độc lập:}\\
E_{ij} = Pr(A_i) \times Pr(B_j) \times N [2]\\
\text{hay}\\
E_{ij} = \frac{\text{(Tổng dòng} \times \text{Tổng cột)}}{\text{Tổng bảng}} [3]   \\
\text{với bảng là bảng contingency}
\end{equation}

Giá trị ${\chi^2}$:

\begin{equation}
\chi^2=\Sigma\frac{(O-E)^2}{E} [2][3]\\
\text{với O là giá trị thực sự và E là giá trị mong đợi}
\end{equation}

Giá trị dof: Degree of freedom

dof cho ${\chi^2}$ độc lập:

\begin{equation}
dof = v = rc - 1 - (r-1) - (c-1) = (r-1)(c-1) [2]\\ = 1
\end{equation}

Chọn mức ý nghĩa:

\begin{equation}
\alpha = 0.05
\end{equation}

Tra bảng Chi Squared với ${\alpha = 0.05, dof = 1}$ ta được critical value ${ = 3.841459}$

Chấp nhận ${H_0}$ nếu
\begin{equation}
\chi^2_v <= 3.841459
\end{equation}

Ta có thể sử dụng `chi2_contingency` của thư viện spicy để tính toán, các giá trị tính được từ thư viện và kết luận là:

p-value là:  0.09827321674727184

chi = 2.733346, critical value = 3.841459

Với mức ý nghĩa 0.05, ta bác bỏ HA và chấp nhận H0.

Kết luận: sex và smoker độc lập.

Ta kiểm tra, không dùng thư viện, được kết quả như sau:

```python

```

    chi_square =  2.997908815661011

      smoker
      sex
      count
      Expected value
      (O_ij - E_ij)^2/E_ij

      0
      no
      male
      391
      402.075773
      0.305099

      1
      yes
      male
      115
      103.924227
      1.180406

      2
      no
      female
      406
      394.924227
      0.310623

      3
      yes
      female
      91
      102.075773
      1.201781

Ta thấy:

\begin{equation}
\chi^2_v = 2.997908815661011 < 3.841459
\end{equation}

Vậy bác bỏ ${H_A}$ với mức ý nghĩa 0.05, chấp nhận ${H_0}$

Kết luận: sex và smoker độc lập

### 5. Insight: Trung bình của 'age', 'bmi', 'children' giữa người có hút thuốc và người không hút thuốc có bằng nhau

Sử dụng z-test ta tính được như sau:

$H_0$: trung bình `age` của người có hút thuốc $=$ trung bình `age` của người không hút thuốc

$H_A$: trung bình `age` của người có hút thuốc $\neq$ trung bình `age` của người không hút thuốc

stat=-1.200, p=0.230

KẾT LUẬN:
Với mức ý nghĩa 0.05, ta chấp nhận Ho, bác bỏ Ha

Trung bình `age` của người có hút thuốc $=$ trung bình `age` của người không hút thuốc

---

$H_0$: trung bình `bmi` của người có hút thuốc $=$ trung bình `bmi` của người không hút thuốc

$H_A$: trung bình `bmi` của người có hút thuốc $\neq$ trung bình `bmi` của người không hút thuốc

stat=-0.047, p=0.962

KẾT LUẬN:
Với mức ý nghĩa 0.05, ta chấp nhận Ho, bác bỏ Ha

Trung bình `bmi` của người có hút thuốc $=$ trung bình `bmi` của người không hút thuốc

---

$H_0$: trung bình `children` của người có hút thuốc $=$ trung bình `children` của người không hút thuốc

$H_A$: trung bình `children` của người có hút thuốc $\neq$ trung bình `children` của người không hút thuốc

stat=0.807, p=0.420

KẾT LUẬN:
Với mức ý nghĩa 0.05, ta chấp nhận Ho, bác bỏ Ha

Trung bình `children` của người có hút thuốc $=$ trung bình `children` của người không hút thuốc

### 6. Insight: Sự phụ thuộc của 'charges' vào 'sex', 'smoker', 'age', 'bmi', 'children'

Ta huấn luyện bằng mô hình OLS Regression:

```python

```

                                OLS Regression Results
    ==============================================================================
    Dep. Variable:                charges   R-squared:                       0.744
    Model:                            OLS   Adj. R-squared:                  0.743
    Method:                 Least Squares   F-statistic:                     580.0
    Date:                Fri, 14 May 2021   Prob (F-statistic):          3.98e-292
    Time:                        20:59:36   Log-Likelihood:                -10164.
    No. Observations:                1003   AIC:                         2.034e+04
    Df Residuals:                     997   BIC:                         2.037e+04
    Df Model:                           5
    Covariance Type:            nonrobust
    ==============================================================================
                     coef    std err          t      P>|t|      [0.025      0.975]
    ------------------------------------------------------------------------------
    const       -1.23e+04   1121.474    -10.968      0.000   -1.45e+04   -1.01e+04
    sex           66.0697    386.570      0.171      0.864    -692.515     824.655
    smoker      2.363e+04    478.849     49.344      0.000    2.27e+04    2.46e+04
    age          260.0358     13.870     18.748      0.000     232.818     287.254
    bmi          327.5600     32.307     10.139      0.000     264.162     390.958
    children     434.8043    160.590      2.708      0.007     119.671     749.938
    ==============================================================================
    Omnibus:                      241.068   Durbin-Watson:                   2.068
    Prob(Omnibus):                  0.000   Jarque-Bera (JB):              592.918
    Skew:                           1.268   Prob(JB):                    1.78e-129
    Kurtosis:                       5.785   Cond. No.                         299.
    ==============================================================================

    Notes:
    [1] Standard Errors assume that the covariance matrix of the errors is correctly specified.

Kết luận:

- Biến `sex` không có ý nghĩa (có thể loại bỏ)
- Biến `smoker` có ý nghĩa đối với mô hình về mặt thống kê (với mức ý nghĩa (***) hay p-value = 0.000)
- Biến `age` có ý nghĩa đối với mô hình về mặt thống kê (với mức ý nghĩa (***) hay p-value = 0.000)
- Biến `bmi` có ý nghĩa đối với mô hình về mặt thống kê (với mức ý nghĩa (***) hay p-value = 0.000)
- Biến `children` không có ý nghĩa (có thể loại bỏ)
- Mô hình có thể giải thích được 74.3% sự thay đổi của biến `charges`
- Mô hình tương đối tốt (p-value = 1.78e-129)

Ta huấn luyện lại mô hình dựa theo kết luận trên

```python

```

                                OLS Regression Results
    ==============================================================================
    Dep. Variable:                charges   R-squared:                       0.742
    Model:                            OLS   Adj. R-squared:                  0.741
    Method:                 Least Squares   F-statistic:                     959.1
    Date:                Fri, 14 May 2021   Prob (F-statistic):          1.63e-293
    Time:                        21:00:36   Log-Likelihood:                -10168.
    No. Observations:                1003   AIC:                         2.034e+04
    Df Residuals:                     999   BIC:                         2.036e+04
    Df Model:                           3
    Covariance Type:            nonrobust
    ==============================================================================
                     coef    std err          t      P>|t|      [0.025      0.975]
    ------------------------------------------------------------------------------
    const      -1.185e+04   1097.537    -10.799      0.000    -1.4e+04   -9698.979
    smoker      2.367e+04    479.257     49.386      0.000    2.27e+04    2.46e+04
    age          262.1450     13.884     18.881      0.000     234.900     289.390
    bmi          326.7252     32.392     10.086      0.000     263.160     390.290
    ==============================================================================
    Omnibus:                      238.957   Durbin-Watson:                   2.060
    Prob(Omnibus):                  0.000   Jarque-Bera (JB):              580.364
    Skew:                           1.263   Prob(JB):                    9.45e-127
    Kurtosis:                       5.741   Cond. No.                         291.
    ==============================================================================

    Notes:
    [1] Standard Errors assume that the covariance matrix of the errors is correctly specified.

```python

```

    Parameters:  const    -11852.720452
    smoker    23668.497446
    age         262.144961
    bmi         326.725200
    dtype: float64

Ta thấy:

- Cứ tăng 1 tuổi thì chi phí y tế cá nhân tăng 262.144961, tăng 1 chỉ số bmi thì tăng 326.725200 chi phí y tế cá nhân
- Riêng với smoker, người có hút thuốc thì có chi phí y tế cá nhân cao hơn người không hút thuốc đến 23668.497446

## II. Thuật toán sử dụng

Trong lab này, nhóm em sử dụng 2 thuật toán chính là Simple linear regression và Support Vector Regression (SVR) (là một dạng mở rộng của Support Vector MachineMachine).

### 1. Cách thức đánh giá mô hình

- Nhóm em sử dụng độ do R-squared để đánh giá mô hình
- Phương pháp lấy mẫu để đánh giá mô hình là K-Fold Cross-Validation với k = 10

### 2. Thuật toán SVR

#### a. Giới thiệu SVR

**Support Vector Machine (SVM)**

SVM là bài toán phân lớp, và đi tìm mặt phân cách sao cho margin tìm được là lớn nhất, đồng nghĩa với việc các điểm dữ liệu an toàn nhất so với mặt phân cách.

**Support Vector Regression (SVR)**

SVR là một biến thể của SVM để dùng cho bài toán hồi quy. SVR tìm cách cực tiểu margin sao cho có thể chứa nhiều điểm dữ liệu nhất có thể.

#### b. Sử dụng SVR từ thư viện Scikit-learn

Trước khi trình bày quy trình, nhóm em thống như quy tắt đặt biến như sau:
- X là một Dataframe đọc từ file train và không bao gồm cột `charges`
- y là Series chứa dữ liệu của cột `charges`
- preds là output sau khi chạy mô hình
- X_test, y_test tương tự như X, y nhưng được đọc từ file test

Để chọn ra mô hình tốt nhất, nhóm tiến hành các bước như sau

#### Bước 1: Tiền xử lí:
- Tiền xử lí X: dùng OrdinalEncoder cho cột `sex` và `smoker`, OnehotEncoder cho `region`, StandardScaler cho các cột có kiểu số
- Tiền xử lý y bằng StandardScaler

Theo các bước tiền xử lí như trên ta được Pipeline như sau:

#### Bước 2:

Chọn tham số cho mô hình SVR với kernel mặc đinh

Với phương pháp lấy mẫu là K-Fold, xem xét độ lỗi trên tập train ta được độ lỗi trung bình đối với từng siêu tham số C và gamma như sau:

Qua đó ta thấy mô hình đạt kết quả tốt nhất trên tập train là 0.828 với C = 10 và gamma = 0.05

#### Bước 3: Huấn luyện mô hình với các siêu tham số vừa tìm được ở trên

Với các siêu tham số trên thì độ chính xác trên tập test khoảng 0.857

#### c. Thử nghiệm tương tự với các kernel khác

Thử nghiệm tương tự với các kernel khác. Kết quả được trình bày cụ thể trong bài làm

#### d. Thử xóa các outlier

Ta thấy nhóm bệnh nhân không hút thuốc có nhiều outlier về chi phí phải trả. Ta thử các outlier này và huấn luyện lại mô hình với các siêu tham số tốt nhất trong các thử nghiệm ở trên. Ta được kết quả như sau:

Ta thấy R-squared trên tập train đã tăng lên khá nhiều, đạt 0.899

Tuy nhiên khi dùng mô hình này để chạy trên tập test thì kết quả chỉ đạt 0.853. Có thể thấy các bệnh nhân này không phải là các trường hợp bất thường. Mà trong thực tế (tập test) vẫn có khá nhiều bệnh nhân giống như vậy - không hút thuốc nhưng chi phí y tế lại cao. Có thể ta cần thêm các thuộc tính khác như thu nhập, môi trường sống có ô nhiễm hay không... mới có thể dự đoán chi phí cho y tế được chính xác hơn

### 3. Dùng Simple Linear Regression từ thư viện Scikit-learn

Tương tự như trên, Pipeline của mô hình là:

Đối với mô hình này ta chỉ đặt độ chính xác R-squared là 0.766

#### Trực quan hóa mô hình

### III. Tham khảo

[1]. [Stephanie - Variance Inflation Factor - Statisticshowto.com](https://www.statisticshowto.com/variance-inflation-factor/)

[2]. https://www3.nd.edu/~rwilliam/stats1/x51.pdf

[3]. https://towardsdatascience.com/gentle-introduction-to-chi-square-test-for-independence-7182a7414a95

[4]. https://scikit-learn.org/0.21/documentation.html

[5]. https://machinelearningmastery.com/how-to-transform-target-variables-for-regression-with-scikit-learn/


# Please download the new class notes.
### Step 1 : Navigate to the directory where your files are stored.
Open a terminal.

Using `cd`, navigate to *inside* the ILAS_Python_for_engineers folder on your computer.

### Step 3 : Update the course notes by downloading the changes
In the terminal type:

>`git add -A`

>`git commit -m "commit"`

>`git fetch upstream`

>`git merge -X theirs upstream/master`

# Introduction to Data Structures and Imported Libraries (Numpy)

 Data Structures

 Lists

    __Indexing__

    Manipulating Lists

       FindingLengthList

       SortingLists

       Removing an Item from a List

       Adding an Item to a List

       Changing a List Entry

    __Nested Data Structures__

    __Iterating over Lists__

    __Indexing when Iterating over Lists__

    `enumerate`

    __`zip`__

    __Example: The Dot Product__

 Libraries

    __The Standard Library__

    __Packages__

       __Importing a Package__

       __Using Package Functions__

       __Reading Function Documentation__

       __Example: `numpy.cos`__

       Namespaces

       __Importing a Function__

    __Using Package Functions to Optimise your Code__

    __Data Structures as Function Arguments__

 The Numpy Array

    __Multi-Dimensional Arrays__

    __Creating a Numpy Array__

    __Indexing into Multi-Dimensional Arrays__-

    Boolean Array Indexing

     __Iterating over Multi-Dimensional Arrays__

    Manipulating Arrays

       Appending Arrays

       Adding Elements to an Array

       Deleting Items from an Array

       Changing Items in an Array

 Magic Functions

 __Stacking Functions__

 __Importing Data from a .csv File to a Numpy Array__

 Summary

 Test-Yourself Exercises

 Review Exercises

### Lesson Goal

Importing data from a .csv file and perfoming calculations on it using library functions.

### Fundamental programming concepts
 - Working with external files to import:
  - Code
  - Data
 - Storing and representing data e.g. arrays and graphs

## Data Structures

In the last seminar we learnt to generate a range of numbers for use in control flow of  a program, using the function `range()`:

       for j in range(20):
           ...

Often we want to manipulate data that is more meaningful than ranges of numbers.

These collections of variables might include:
 - the results of an experiment
 - a list of names
 - the components of a vector
 - a telephone directory with names and associated numbers.

Python has different __data structures__ that can be used to store and manipulate these values.

Like variable types (`string`, `int`,`float`...) different data structures behave in different ways.

Today we will learn to use `list`s and `array`'s.

Example

If we want to store the names of students in a laboratory group,
rather than representing each students using an individual string variable, we could use a list of names.

```python
lab_group0 = ["Yukari", "Sajid", "Hemma", "Ayako"]
lab_group1 = ["Sara", "Mari", "Quang", "Sam", "Ryo", "Nao", "Takashi"]

print(lab_group0)
print(lab_group1)
```

    ['Yukari', 'Sajid', 'Hemma', 'Ayako']
    ['Sara', 'Mari', 'Quang', 'Sam', 'Ryo', 'Nao', 'Takashi']

This is useful because we can perform operations on lists such as:
 - checking its length (number of students in a lab group)
 - sorting the names in the list into alphabetical order
 - making a list of lists (we call this a *nested list*):

```python
lab_groups = [lab_group0, lab_group1]
```

## Lists

A list is a sequence of data.

We call each item in the sequence an *element*.

A list is constructed using square brackets:

```python
a = [1, 2, 3]
```

A `range` can be converted to a list with the `list` function (casting).

```python
print(list(range(10)))
```

    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

A list can hold a mixture of types (`int`, `string`....).

```python
a = [1, 2.0, "three"]
```

An empty list is created by

```python
my_list = []
```

A list of length 5 with repeated values can be created by

```python
my_list = ["Hello"]*5
print(my_list)
```

    ['Hello', 'Hello', 'Hello', 'Hello', 'Hello']

We can check if an item is in a list using the function `in`:

```python
print("Hello" in my_list)
print("Goodbye" in my_list)
```

    True
    False

### Indexing

Lists store data in order.

We can select a single element or multiple elements of a list using the __index__ of the element(s).

You are familiar with this process; it is the same as selecting individual characters of a string:

```python
word = "string"

letter = word[1]

print(letter)
```

    t

```python
lab_group0 = ["Sara", "Mari", "Quang", "Sam", "Ryo", "Nao", "Takashi"]

first_member = lab_group0[0]

print(first_member)
```

    Sara

If we select multiple elements they are returned as a list:

```python
lab_group0 = ["Sara", "Mari", "Quang", "Sam", "Ryo", "Nao", "Takashi"]

first_members = lab_group0[0:2]

print(first_members)
```

    ['Sara', 'Mari']

We can select the individual characters of a string using a second index.

For example to select the first letter of the second group member's name:

```python
lab_group0 = ["Sara", "Mari", "Quang", "Sam", "Ryo", "Nao", "Takashi"]

letter = lab_group0[1][0]

print(letter)
```

    M

### Manipulating Lists

There are many functions for manipulating lists.

Many of these functions apply to other data structures.

### Finding the Length of a List

We can find the length (number of items) of a list using the function `len()`, by including the name of the list in the brackets.

In the example below, we find the length of the list `lab_group0`.

```python
lab_group0 = ["Sara", "Mari", "Quang"]

size = len(lab_group0)

print("Lab group members:", lab_group0)

print("Size of lab group:", size)

print("Check the Python object type:", type(lab_group0))
```

    Lab group members: ['Sara', 'Mari', 'Quang']
    Size of lab group: 3
    Check the Python object type: <class 'list'>

### Sorting Lists

To sort the list we use the function `sorted()`.

#### Sorting Numerically

If the list contains numerical variables, the numbers is sorted in ascending order.

```python
numbers = [7, 1, 3.0]

print(numbers)

numbers = sorted(numbers)

print(numbers)
```

    [7, 1, 3.0]
    [1, 3.0, 7]

__Note:__ We can sort a list with mixed numeric types (e.g. `float` and `int`).

However, we cannot sort a list with types that cannot be sorted by the same ordering rule.

(e.g. `numbers = sorted(["7", 1, 3.0])` causes an error.)

```python

```

#### Sorting Alphabetically

If the list contains strings of alphabet characters, the list is sorted by alphabetical order.

```python
lab_group0 = ["Sara", "Mari", "Quang"]

print(lab_group0)

lab_group0 = sorted(lab_group0)

print(lab_group0)
```

    ['Sara', 'Mari', 'Quang']
    ['Mari', 'Quang', 'Sara']

As with `len()` we include the name of the list we want to sort in the brackets.

`sort` is known as a 'method' of a `list`.

If we suffix a list with `.sort()`, it performs an *in-place* sort.

```python
lab_group0 = ["Sara", "Mari", "Quang"]

print(lab_group0)

#lab_group0 = sorted(lab_group0)
lab_group0.sort()

print(lab_group0)
```

    ['Sara', 'Mari', 'Quang']
    ['Mari', 'Quang', 'Sara']

__Try it yourself__

In the cell provided in your textbook create a list of __numeric__ or __string__ values.

Sort the list using `sorted()` __or__ `.sort()`.

Print the sorted list.

Print the length of the list using `len()`.

```python
# Sorting a list
```

### Removing an Item from a List

We can remove items from a list using the method `pop`.

We place the index of the element we wish to remove in brackets.

```python
lab_group0 = ["Sara", "Mari", "Quang", "Sam", "Ryo"]
print(lab_group0)

# Remove the second student from the list: lab_group (remember indexing starts from 0 so 1 is the second element)

lab_group0.pop(1)
print(lab_group0)
```

    ['Sara', 'Mari', 'Quang', 'Sam', 'Ryo']
    ['Sara', 'Quang', 'Sam', 'Ryo']

```python
# By default, pop removes the last element

lab_group0.pop()
print(lab_group0)
```

    ['Sara', 'Quang', 'Sam']

```python
# Pop can by used to assign the removed value to a variable name

group_member = lab_group0.pop(1)
print(lab_group0)
print(group_member)
```

    ['Sara', 'Sam']
    Quang

### Adding an Item to a List

We can add items to a list using the method `insert`.

We place the desired index of new element in brackets.

```python
# Add new student "Mark" to the list
lab_group0.insert(2, "Mark")
print(lab_group0)
```

    ['Sara', 'Sam', 'Mark']

We can add items at the end of a list using the method `append`.

We place the element we want to add to the end of the list in brackets.

```python
# Add new student "Lia" at the end of the list
lab_group0.append("Lia")
print(lab_group0)
```

    ['Sara', 'Sam', 'Mark', 'Lia']

### Changing a List Entry.
We can change the entry of a list using indexing.

```python
lab_group0[3] = "Am"
print(lab_group0)

# Adding and removing items from a list.
```

    ['Sara', 'Sam', 'Mark', 'Am']

__Try it yourself__

In the cell provided in your textbook.

Remove "Sara" from the list.

Print the new list.

Add a new lab group member, Tom, to the list.

Print the new list.

```python
lab_group0 = ["Sara", "Mari", "Quang", "Sam", "Ryo"]
print(lab_group0)

# Adding and removing items from a list.
```

    ['Sara', 'Mari', 'Quang', 'Sam', 'Ryo']

### Nested Data Structures: Lists of Lists

A *nested list* is a list within a list.

(Recall *nested loops* from the last seminar 1; Control Flow).

To access a __single element__ we need as many indices as there are levels of nested list.

This is more easily explained with an example:

`lab_groups` is a nested list containing the lists:
 - `lab_group0`
 - `lab_group1`
 - `lab_group2`

```python
lab_group0 = ["Sara", "Mika", "Ryo", "Am"]
lab_group1 = ["Hemma", "Miri", "Quy", "Sajid"]
lab_group2 = ["Adam", "Yukari", "Farad", "Fumitoshi"]

lab_groups = [lab_group0, lab_group1, lab_group2]
```

To select and element from `lab_group1` we:
- first give the index of `lab_group1` in th list `lab_groups`
- second give the index of the element within `lab_group1`

```python
group = lab_groups[1]
print(group)

name = lab_groups[1][2]
print(name)
```

    ['Hemma', 'Miri', 'Quy', 'Sajid']
    Quy

### Iterating over Lists

Looping over each item in a list is called *iterating*.

To iterate over a list of the lab group we  can use a `for` loop.

In the following example, each iteration, variable `d` takes the value of the next item in the list:

```python
for d in [1, 2.0, "three"]:
    print("the value of d is:", d)
```

    the value of d is: 1
    the value of d is: 2.0
    the value of d is: three

We could also express this as:

```python
data = [1, 2.0, "three"]

for d in data:
    print("the value of d is:", d)
```

    the value of d is: 1
    the value of d is: 2.0
    the value of d is: three

Iterating backwards over a list can be acheived using the built in `reversed` function.

```python
data = [1, 2.0, "three"]

for d in reversed(data):
    print("the value of d is:", d)
```

    the value of d is: three
    the value of d is: 2.0
    the value of d is: 1

__Try it yourself__

In the cell provided in your textbook *iterate* over the list `data = [1, 2.0, "three"]`.

Each time the code loops:
1. print the value of data __cast as a string__ (Seminar 1 Data Types and Operators)
1. print the variable type
(to demonstrate that the variable has been cast. Note that otherwise the variable appears to remain unchanged).

```python
# Iterate over a list and cast each item as a string
data = [1, 2.0, "three"]
```

### Indexing when Iterating over Lists
Indexing can be useful when iterating over a list.

For example, we can select a range of elements to iterate over:

```python
lab_group0 = ["Sara", "Mari", "Quang", "Sam", "Ryo", "Nao", "Takashi"]

for member in lab_group0[2:5]:
    print("name:", member)
```

    name: Quang
    name: Sam
    name: Ryo

A third value can used to choose a step size (similar to `range()`).

For example, if we want to choose every other lab member we use step size, 2:

```python
lab_group0 = ["Sara", "Mari", "Quang", "Sam", "Ryo", "Nao", "Takashi"]

for member in lab_group0[::2]:
    print("name:", member)
```

    name: Sara
    name: Quang
    name: Ryo
    name: Takashi

__Note:__

Some data structures that support *iterating* but do not support *indexing*.

e.g. dictionaries, which we will learn about later.

When possible, it is better to iterate over a list rather than use indexing.

### `enumerate()`
The function `enumerate` can be used to return the index of each element.

This information is cast as a list to allow us to read it.

```python
lab_group0 = ["Sara", "Mari", "Quang", "Sam", "Ryo", "Nao", "Takashi"]
a = enumerate(lab_group0)
b = list(enumerate(lab_group0))
print(a)
print(b)

```

    <enumerate object at 0x7fc8b8810678>
    [(0, 'Sara'), (1, 'Mari'), (2, 'Quang'), (3, 'Sam'), (4, 'Ryo'), (5, 'Nao'), (6, 'Takashi')]

```python
string = "string"
a = list(enumerate(string))
print(a)
```

    [(0, 's'), (1, 't'), (2, 'r'), (3, 'i'), (4, 'n'), (5, 'g')]

### Iterating Over Multiple Lists Using `zip()`
It can be very useful to iterate through multiple lists within the same loop. than one list.

For example if we have a list of group members and a list of their scores for an assignemt, we can print the score that corresponds to each lab member:

```python
lab_group0 =  ["Sara", "Mari", "Quang", "Sam", "Ryo", "Nao", "Takashi"]

assignment1 = [72, 56, 65, 52, 71, 60]

for member, score in zip(lab_group0, assignment1):

    print(member, ": score =", score)
```

    Sara : score = 72
    Mari : score = 56
    Quang : score = 65
    Sam : score = 52
    Ryo : score = 71
    Nao : score = 60

In this example

`member` is the name given to the *current* value from the list `lab_group0`

`score` is the names given to the *current* value from the list `assignment1`.

We can include any number of lists in `zip`.

For example it may be useful to print the combined score a lab member has achieved for all assigments this semester:

```python
lab_group0 =  ["Sara", "Mari", "Quang", "Sam", "Ryo", "Nao", "Takashi"]

assignment1 = [72, 56, 65, 52, 71, 60]
assignment2 = [52, 61, 73, 55, 62, 55]
assignment3 = [71, 71, 70, 66, 61, 71]

for member, score1, score2, score3 in zip(lab_group0,
                                          assignment1,
                                          assignment2,
                                          assignment3):
    print(member, ": score =", (score1 + score2 + score3))
```

    Sara : score = 195
    Mari : score = 188
    Quang : score = 208
    Sam : score = 173
    Ryo : score = 194
    Nao : score = 186

### Example: The Dot Product (Representing Vectors using Lists)

__Vector:__ A quantity with magnitude and direction.

The position vector $\mathbf{r}$ indicates the position of a point in 3D space.
$\mathbf{r}$ can be expressed in terms of x,y, and z-directions.

$$
\mathbf{r} = x\mathbf{i} + y\mathbf{j} + z\mathbf{k}
$$

$\mathbf{i}$ is the displacement one unit in the x-direction

$\mathbf{j}$ is the displacement one unit in the y-direction

$\mathbf{k}$ is the displacement one unit in the z-direction

We can conveniently express $\mathbf{r}$ in matrix (or basis vector) form using the coefficients $x, y$ and $z$:
$$
\mathbf{r} = [x, y, z]
$$

__...which looks a lot like a Python list!__

You will encounter 3D vectors a lot in your engineering studies.

They are used to describe many physical quantities, e.g. force.

The __dot product__ is a really useful algebraic operation.

It takes two equal-length *sequences of numbers* (often coordinate vectors) and returns a single number.

__ALGEBRAIC REPRESENTATION OF THE DOT PRODUCT__

The dot product of two $n$-length-vectors:

 $ \mathbf{A} = [A_1, A_2, ... A_n]$

 $ \mathbf{B} = [B_1, B_2, ... B_n]$

\begin{align}
\mathbf{A} \cdot \mathbf{B} = \sum_{i=1}^n A_i B_i
\end{align}

So the dot product of two 3D vectors:

 $ \mathbf{A} = [A_x, A_y, A_z]$

 $ \mathbf{B} = [B_x, B_y, B_z]$

\begin{align}
\mathbf{A} \cdot \mathbf{B} &= \sum_{i=1}^n A_i B_i \\
&= A_x B_x + A_y B_y + A_z B_z
\end{align}

__Example : Dot Product__

Let's write a program to solve this using a Python `for` loop.

1. We initailise a variable, `dot_product` with a value = 0.0.

1. With each iteration of the loop:

`dot_product +=` the product of `a` and `b`.

```python
# Example : Dot Product

A = [1.0, 3.0, -5.0]
B = [4.0, -2.0, -1.0]

# Create a variable called dot_product with value, 0.0
dot_product = 0.0

# Update the value each time the code loops
for a , b in zip(A, B):
    dot_product += a * b

# Print the solution
print(dot_product)
```

    3.0

(Solution in 02_DataStructures_LibraryFunctions_SOLS.ipynb)

__Check Your Solution:__

The dot product $\mathbf{A} \cdot \mathbf{B}$:

 $ \mathbf{A} = [1, 3, −5]$

 $ \mathbf{B} = [4, −2, −1]$

\begin{align}
      {\displaystyle {\begin{aligned}\ [1,3,-5]\cdot [4,-2,-1]&=(1)(4)+(3)(-2)+(-5)(-1)\\& = 4 \qquad - 6 \qquad + 5 \\&=3\end{aligned}}}
\end{align}

## Libraries

One of the most important concepts in good programming is to reuse code and avoid repetitions.

Python, like other modern programming languages, has an extensive *library* of built-in functions.

These functions are designed, tested and optimised by the developers of the Python langauge.

We can use these functions to make our code shorter, faster and more reliable.

## The Standard Library

Python has a large standard library.

e.g. `print()` takes the __input__ in the parentheses and __outputs__ a visible representation.

They are listed on the Python website:
https://docs.python.org/3/library/functions.html

We could write our own code to find the minimum of a group of numbers

```python
x0 = 1
x1 = 2
x2 = 4

x_min = x0
if x1 < x_min:
    x_min = x1
if x2 < x_min:
    x_min = x2

print(x_min)
```

    1

However, it is much faster to use the build-in function:

```python
print(min(1,2,4))
```

    1

Python built-in functions are simply a collection of Python (.py) files called 'modules'.

These files are stored on the computer you are using.

__Function:__

A function is a piece of code that is called by name.

It can be *passed* data to operate on (i.e., the parameters) and can optionally *return* data (the return value).

__Example__

```python
a = [5, 2, 3, 1, 4]
print(sorted(a))
```

    [1, 2, 3, 4, 5]

__Method:__
A method is a piece of code that is called by name.

It is already associated with an object type (e.g. a list) so it is expressed after a . dot at the end of the object name.

It mostly behaves the same as a function  except:
- It is automatically passed for the object which it is attached to.
- (It can only operate on objects that contain the method. It can operate on data insde of that class.)

__Example__

```python
a = [1, 5, 2, 7, 5]
a.sort()
print(a)
```

    [1, 2, 5, 5, 7]

A quick google search for "python function to sum all the numbers in a list"...

https://www.google.co.jp/search?q=python+function+to+sum+all+the+numbers+in+a+list&rlz=1C5CHFA_enJP751JP751&oq=python+function+to+sum+&aqs=chrome.0.0j69i57j0l4.7962j0j7&sourceid=chrome&ie=UTF-8

...returns the function `sum()`.

`sum()` finds the sum of the values in a data structure.

```python
print(sum([1,2,3,4,5]))

a = [1,2,3,4,5]
print(sum(a))
```

    15
    15
    15

The function `max()` finds the maximum value in data structure.

## Packages

The standard library tools are available in any Python environment.

More specialised libraries, called packages, are available for more specific tasks

e.g. solving trigonometric functions.

Packages contain functions and constants.

We install the packages to use them.

Two widely used packages for mathematics, science and engineeirng are `NumPy` and `SciPy`.

These are already installed as part of Anaconda.

A package is a collection of Python modules:
- a __module__ is a single Python file
- a __package__ is a directory of Python modules.
(It contains an __init__.py file, to distinguish it from folders that are not libraries).

The files that are stored on your computer when Numpy is installed:

https://github.com/numpy/numpy

### Importing a Package

To use an installed package, we  simply `import` it.

```python
import numpy

x = 1

y = numpy.cos(x)

print(y)

print(numpy.pi)
```

    0.540302305868
    3.141592653589793

The `import` statement must appear before the use of the package in the code.

        import numpy

After this, any function in `numpy` can be called as:

        `numpy.function()`

and, any constant in `numpy` can be called as:

        `numpy.constant`.

There are a many mathematical functions available.

https://docs.scipy.org/doc/numpy-1.13.0/reference/routines.math.html

We can change the name of a package e.g. to keep our code short and neat.

Using the __`as`__ keyword:

```python
import numpy as np
print(np.pi)
```

    3.141592653589793

We only need to import a package once, at the start of the program or notebook.

## Using Package Functions.

Let's learn to use `numpy` functions in our programs.

```python
# Some examples Numpy functions with their definitions (as given in the documentation)

x = 1

# Trigonometric sine
print(np.sin(x))

# Compute tangent
print(np.tan(x))

# Trigonometric inverse tangent
print(np.arctan(x))

```

    0.841470984808
    1.55740772465
    0.785398163397

```python
x = 1

# Convert angles from radians to degrees
degrees = np.degrees(x)
print(degrees)

# Convert angles from degrees to radians
radians = np.radians(degrees)
print(radians)
```

    57.2957795131
    1.0

## Reading Function Documentation

Online documentation can be used to find out:
- what to include in the () parentheses
- allowable data types to use as arguments
- the order in which arguments should be given

A google search for 'numpy functions' returns:

https://docs.scipy.org/doc/numpy-1.13.0/reference/routines.math.html

(this list is not exhaustive).

__Try it yourself:__

 Find a function in the Python Numpy documentation that matches the function definition and use it to solve the following problem:

Given the “legs” of a right angle triangle, return its hypotenuse.
 If  the lengths of the two shorter sides of a right angle triangle are 6 units  and 3 units, what is the length of the hypotenuse?

```python
# The “legs” of a right angle triangle are 6 units and 3 units,
# Return its hypotenuse in units.
```

### Example : numpy.cos
Documentation : https://docs.scipy.org/doc/numpy-1.13.0/reference/routines.math.html

The documentation tells us the following information...

##### What the function does.
"Cosine element-wise."

##### All possible function arguments (parameters)

>numpy.cos(x, /, out=None, *, where=True, casting='same_kind', order='K', dtype=None, subok=True [, signature, extobj ])

In the () parentheses following the function name are:
- *positional* arguments (required)
- *keyword* arguments (with a default value, optionally set). Listed after the `/` slash.
- arguments that must be explicitly named. Listed after the `*` star.

(including arguments without a default value.  Listed in `[]` brackets.)

##### Function argument definitions and acceptable forms.

x : array_like *(it can be an `int`, `float`, `list` or `tuple`)*

out : ndarray, None, or tuple of ndarray and None, optional

where : array_like, optional

##### What the function returns
__y__ : ndarray

        The corresponding cosine values.

Let's look at the function numpy.degrees:
https://docs.scipy.org/doc/numpy-1.13.0/reference/generated/numpy.degrees.html

What does the function do?

What __arguments__ does it take (and are there any default arguments)?

How would we __write__ the function when __calling__ it (accept defaults)?

What __data type__ should our input be?

## Namespaces

By prefixing `cos` with `np`, we are using a *namespace* (which in this case is `np`).

The namespace shows we want to use the `cos` function from the Numpy package.

If `cos` appears in more than one package we import, then there will be more than one `cos` function available.

We must make it clear which `cos` we want to use.

Often, functions with the same name, from different packages, will use a different algorithms for performing the same or similar operation.

They may vary in speed and accuracy.

In some applications we might need an accurate method for computing the square root, for example, and the speed of the program may not be important. For other applications we might need speed with an allowable compromise on accuracy.

Below are two functions, both named `sqrt`.

Both functions compute the square root of the input.

 - `math.sqrt`, from the package, `math`, gives an error if the input is a negative number. It does not support complex numbers.
 - `cmath.sqrt`, from the package, `cmath`, supports complex numbers.

```python
import math
import cmath
print(math.sqrt(4))
#print(math.sqrt-5)
#print(cmath.sqrt(-5))
```

    2.0

Two developers collaborating on the same program might choose the same name for two functions that perform similar tasks.

If these functions are in different modules, there will be no name clash since the module name provides a 'namespace'.

## Importing a Function
Single functions can be imported without importing the entire package e.g. use:

        from numpy import cos

instead of:

        import numpy

After this you call the function without the numpy prefix:

```python
from numpy import cos

cos(x)
```

    0.54030230586813977

Be careful when doing this as there can be only one definition of each function.
In the case that a function name is already defined, it will be overwritten by a more recent definition.

```python
from cmath import sqrt
print(sqrt(-1))
from math import sqrt
#print(sqrt(-1))
```

    1j

A potential solution to this is to rename individual functions or constants when we import them:

```python
from numpy import cos as cosine

cosine(x)
```

    0.54030230586813977

```python
from numpy import pi as pi
pi
```

    3.141592653589793

This can be useful when importing functions from different modules:

```python
from math import sqrt as square_root
from cmath import sqrt as complex_square_root

print(square_root(4))
print(complex_square_root(-1))
```

    2.0
    1j

Function names should be chosen wisely.
 - relevant
 - concise

##### Try it yourself
In the cell below, copy and paste the `bisection` function you wrote for __Seminar 4: Review Excercise: Using Functions as Function Arguments.__

Demonstrate that your `bisection` function works correctly by finding the zero of the Numpy $\cos(x)$ function that lies in the interval $x_1=0$ to $x_2=3$.

```python
# Bisection
```

## Using Package Functions to Optimise your Code

Let's look at some examples of where Numpy functions can make your code shorter and neater.

The mean of a group of numbers

```python
x_mean = (1 + 2 + 3)/3
```

Using Numpy:

```python
x_mean = np.mean([1, 2, 3])
```

## Data Structures as Function Arguments.

Notice that the Numpy function `mean` take a lists as its argument.

The list data structure is required for the function to work.

```python
ls = [1, 2, 3]
x_mean = np.mean(ls)
```

### Elementwise Functions
Numpy functions often operate *elementwise*.

 This means if the argument is a list, they will perform the same function on each element of the list.

For example, to find the square root of each number in a list, we can use:

```python
a = [9, 25, 36]
print(np.sqrt(a))
```

    [ 3.  5.  6.]

Elementwise operation can be particularly important when performing basic mathematical operations:

```python
a = [1, 2, 3]
b = [4, 5, 6]
import numpy as np

print(a + b)
print(np.add(a,b))
```

    [1, 2, 3, 4, 5, 6]
    [5 7 9]

Numpy has its own data structure that is more suitable for handling numerical data.

## The Numpy `Array`

A numpy array is a grid of values, *all of the same type*.

### Why do we need another data structure?

Python lists hold 'arrays' of data.

Lists are very flexible. e.g. holding mixed data type.

There is a trade off between flexibility and performance e.g. speed.

Science engineering and mathematics problems often involve large amounts of data and numerous operations.

We therefore use specialised functions and data structures for numerical computation.

To create an array we use the Numpy `np.array()` function.

We can create an array in a number of ways.

For example we can convert a list to an array.

```python
c = [4.0, 5, 6.0]

d = np.array(c)

print(type(c))
print(type(d))
print(d.dtype)

print(c + c)
print(d + d)
```

    <class 'list'>
    <class 'numpy.ndarray'>
    float64
    [4.0, 5, 6.0, 4.0, 5, 6.0]
    [  8.  10.  12.]

The method `dtype` tells us the type of the data contained in the array.

## Multi-Dimensional Arrays.

Unlike the data types we have studied so far, arrays can have multiple dimensions.

__`shape`:__ a *tuple* of *integers* giving the *size* of the array along each *dimension*.

__`tuple`:__ A data structure from which you cannot add or remove elements without creating a new tuple (e.g. connecting two tuples).
You cannot change the value of a single tuple element e.g. by indexing.
A tuple is created by enclosing a set of numbers in () parentheses.

We define the dimensions of an array using square brackets

```python
# 1-dimensional array
a = np.array([1, 2, 3])

# 2-dimensional array
b = np.array([[1, 2, 3], [4, 5, 6]])

b = np.array([[1, 2, 3],
              [4, 5, 6]])

print(a.shape)
print(b.shape)

```

    (3,)
    (2, 3)

```python
# 2-dimensional array
c = np.array([[1, 2, 3]])

# 2-dimensional array
d = np.array([[1],
              [4]])

print(c.shape)
print(d.shape)
```

    (1, 3)
    (2, 1)

```python
# 3-dimensional array

c = np.array(
    [[[1, 1],
      [1, 1]],

     [[1, 1],
      [1, 1]]])

print(c.shape)

c = np.array(
    [[[1, 1],
      [1, 1]],

     [[1, 1],
      [1, 1]],

     [[1, 1],
      [1, 1]]])

print(c.shape)
```

    (2, 2, 2)
    (3, 2, 2)

```python
# 3-dimensional array

c = np.array(
    [[[1, 1],
      [1, 1]],

     [[1, 1],
      [1, 1]]])

# 4-dimensional array
d = np.array(
    [[[[1, 1],
       [1, 1]],

      [[1, 1],
       [1, 1]]],

      [[[1, 1],
       [1, 1]],

      [[1, 1],
       [1, 1]]]])

print(c.shape)
print(d.shape)
```

    (2, 2, 2)
    (2, 2, 2, 2)

## Creating a Numpy Array.

We don't always have to manually create the individual elements of an array.

There are several other ways to do this.

For example, if you don’t know what data you want to put in your array you can initialise it with placeholders and load the data you want to use later.

```python
# Create an array of all zeros
# The zeros() function argument is the shape.
# Shape: tuple of integers giving the size along each dimension.

a = np.zeros(5)
print(a)

print()

a = np.zeros((2,2))
print(a)
```

    [ 0.  0.  0.  0.  0.]

    [[ 0.  0.]
     [ 0.  0.]]

```python
# Create an array of all ones

b = np.ones(5)
print(b)

print()

b = np.ones((1, 4))
print(b)

```

    [ 1.  1.  1.  1.  1.]

    [[ 1.  1.  1.  1.]]

```python
# Create an array of elements with the same value
# The full() function arguments are
# 1) Shape: tuple of integers giving the size along each dimension.
# 2) The constant value

y = np.full((1,1), 3)
print(y)
print(y.shape)

print()

y = np.full((2,2), 4)
print(y)
```

    [[3]]
    (1, 1)

    [[4 4]
     [4 4]]

```python
# Create a 1D array of evenly spaced values
# The arange() function arguments are the same as the range() function.
# Shape: tuple of integers giving the size along each dimension.

z = np.arange(5,10)
print(z)

print()

z = np.arange(5, 10, 2)
print(z)
```

    [5 6 7 8 9]

    [5 7 9]

```python
# Create a 1D array of evenly spaced values
# The linspace() function arguments are
# The lower limit of the range of values
# The upper limit of the range of values (inclusive)
# The desired number of equally spaced values

z = np.linspace(-4, 4, 5)
print(z)
```

    [-4. -2.  0.  2.  4.]

```python
# Create an empty matrix
# The empty() function argument is the shape.
# Shape: tuple of integers giving the size along each dimension.
import numpy as np
x = np.empty((4))
print(x)

print()

x = np.empty((4,4))
print(x)
```

    [ 4.  2.  2.  4.]

    [[  4.94065646e-324   4.94065646e-324   4.94065646e-324   4.94065646e-324]
     [  4.94065646e-324   4.94065646e-324   4.94065646e-324   4.94065646e-324]
     [  4.94065646e-324   4.94065646e-324   4.94065646e-324   4.94065646e-324]
     [  4.94065646e-324   4.94065646e-324   4.94065646e-324   4.94065646e-324]]

```python
# Create a constant array
# The second function argument is the constant value

c = np.full(6, 8)
print(c)

print()

c = np.full((2,2,2), 7)
print(c)

```

    [8 8 8 8 8 8]

    [[[7 7]
      [7 7]]

     [[7 7]
      [7 7]]]

## Indexing into Multi-Dimensional Arrays.

We can index into an array exactly the same way as the other data structures we have studied.

```python
x = np.array([1, 2, 3, 4, 5])

# Select a single element
print(x[4])

# Select elements from 2 to the end
print(x[2:])
```

    5
    [3 4 5]

For an n-dimensional (nD) matrix we need n index values to address an element or range of elements.

Example: The index of a 2D array is specified with two values:
- first the row index
- then the column index.

Note the order in which dimensions are addressed.

```python
# 2 dimensional array

y = np.array([[1, 2, 3],
              [4, 5, 6]])

# Select a single element
print(y[1,2])

# Select elements that are both in rows 1 to the end AND columns 0 to 2
print(y[1:, 0:2])
```

    6
    [[4 5]]

We can address elements by selecting a range with a step:

For example the index:

`z[0, 0:]`

selects every element of row 0 in array, `z`

The index:

`z[0, 0::2]`

selects every *other* element of row 0 in array, `z`

```python
# 2 dimensional array

z = np.zeros((4,8))

# Change every element of row 0
z[0, 0:] = 10

# Change every other element of row 1
z[1, 0::2] = 10

print(z)
```

    [[ 10.  10.  10.  10.  10.  10.  10.  10.]
     [ 10.   0.  10.   0.  10.   0.  10.   0.]
     [  0.   0.   0.   0.   0.   0.   0.   0.]
     [  0.   0.   0.   0.   0.   0.   0.   0.]]

```python
z = np.zeros((4,8))

# Change the last 4 elements of row 2, in negative direction
# You MUST include a step to count in the negative direction
z[2, -1:-5:-1] = 10

# Change every other element of the last 6 elements of row 3
# in negative direction
z[3, -2:-7:-2] = 10

print(z)
```

    [[  0.   0.   0.   0.   0.   0.   0.   0.]
     [  0.   0.   0.   0.   0.   0.   0.   0.]
     [  0.   0.   0.   0.  10.  10.  10.  10.]
     [  0.   0.  10.   0.  10.   0.  10.   0.]]

```python
# 3-dimensional array

c = np.array(
    [[[2, 1, 4],
      [2, 6, 8]],

     [[0, 1, 5],
      [7, 8, 9]]])

print(c[0, 1, 2])

```

    8

Where we want to select all elements in one dimension we can use :

__Exception__: If it is the last element , we can omit it.

```python
print(c[0, 1])

print(c[0, :, 1])
```

    [2 6 8]
    [1 6]

### BooleanArrayIndexing

Recall that we can use *conditional operators* to check the value of a single variable against a condition.

The value returned is a Boolean True or False value.

```python
a = 4
print('a < 2:', a < 2)
print('a > 2:', a > 2)
```

    a < 2: False
    a > 2: True

If we instead use *conditional operators* to check the value of an array against a condition.

The value returned is an *array* of Boolean True or False values.

```python
a = np.array([[1,2],
              [3, 4],
              [5, 6]])

idx = a > 2

print(idx)

```

    [[False False]
     [ True  True]
     [ True  True]]

A particular elements of an array can be are specified by using a boolean array as an index.

Only the values of the array where the boolean array is `True` are selected.

The varaible `idx` can therefore now be used as the index to select all elements greater than 2.

```python
print(a[idx])
```

    [3 4 5 6]

To do the whole process in a single step

```python
print(a[a > 2])
```

    [3 4 5 6]

To apply multiple conditions, use () parentheses to sperate different conditions.

Use `&` for elementwise `and`.

Use `|` for elementwise `or`.

```python
x = np.array([[4, 2, 3, 1],
              [2, 4, 2, 8],
              [2, 3, 3, 27],
              [4, 1, 4, 64]])

# elements of x that are greater then 2 AND less than 10
print(x[(2 < x) & (x < 10)])

# elements of x that are less then 2 OR greater than 10
print(x[(2 < x) & (x < 10)])
```

    [4 3 4 8 3 3 4 4]
    [4 3 4 8 3 3 4 4]

Multiple conditions can also be applied to a subsection of an array.

For example to select elements $>2$ and $<4$ in the first row of `x` only (`x[0]`):

```python
x = np.array([[4, 2, 3, 1],
              [2, 4, 2, 8],
              [2, 3, 3, 27],
              [4, 1, 4, 64]])

print(x[0][(2 < x[0]) & (x[0] < 4)])

```

    [3]

## Iterating over Multi-Dimensional Arrays.
We can iterate over a 1D array in the same way as the data structures we have previously studied.

```python
A = np.array([1, 2, 3, 4, 5])
```

```python
for a in A:
    print(a)
```

    1
    2
    3
    4
    5

To loop through individual elements of a multi-dimensional array, we use a nested loop for each dimension of the array.

```python
B = np.array([[1, 2, 3],
              [4, 5, 6]])

for row in B:
    print("-----")
    for col in row:
        print(col)
```

    -----
    1
    2
    3
    -----
    4
    5
    6

## Manipulating Arrays
We can use many of the same operations to manipulate arrays as we use for lists.

However, it is important to note a few subtle differences in how array manipulations behave.

```python
# Length of an array

a = np.array([1, 3, 4, 17, 3, 21, 2, 12])

b = np.array([[1, 3, 4, 17],
              [3, 21, 2, 12]])

print(len(a))
print(len(b))

```

    8
    2

Note the length is the length of the first dimension (e.g. indexing).

```python
# Sort an array

a = np.array([1, 3, 4, 17, 3, 21, 2, 12])

b = np.array([[1, 3, 4, 17],
              [3, 21, 2, 12]])

# The function sorted applies to 1D data structures only
print(sorted(a))
print(sorted(b[1]))

# The method sort() applies to arrays of any size
a.sort()
b.sort()

print(a)
print(b)
```

    [1, 2, 3, 3, 4, 12, 17, 21]
    [2, 3, 12, 21]
    [ 1  2  3  3  4 12 17 21]
    [[ 1  3  4 17]
     [ 2  3 12 21]]

Arrays are *immutable* (unchangeable).

Technically you cannot add or delete items of an array.

However, you can make a *new* array (which may have the same name as the original array), with the values ammended as required:

#### Appending Arrays
Appending connects array-like (integer, list....) value  to the *end* of the original array.

By default, 2D arrays are appended as if joining lists.
The new array is a 1D array

```python
# 2D array
a = np.array([[0], [1], [2]])
print(a)
print()

# 2D array
b = np.array([[3], [4]])
print(b)
print()

# 1D array
c = np.array([3, 4])
print(b)
print()

# integer
d = 1

print(f"original 2D array shapes: a = {a.shape}, b = {b.shape}")
print()

X = np.append(a, b)
print(X)
print(f"new array shape: {a.shape}")
print()

X = np.append(b, d)
print(X)
print(f"new array shape: {a.shape}")
print()

X = np.append(c, d)
print(X)
print(f"new array shape: {a.shape}")
print()
```

    [[0]
     [1]
     [2]]

    [[3]
     [4]]

    [[3]
     [4]]

    original 2D array shapes: a = (3, 1), b = (2, 1)

    [0 1 2 3 4]
    new array shape: (3, 1)

    [3 4 1]
    new array shape: (3, 1)

    [3 4 1]
    new array shape: (3, 1)

The axis on which to append an array can be optionally specified.

e.g. 2D array:
 - 0: columns
 - 1: rows

The arrays must have the same shape, except in the dimension corresponding to the specified axis

```python
# 2D array
a = np.array([[0], [1], [2]])
print(a)
print()

# 2D array
b = np.array([[3], [4]])
print(b)
print()

new2d = np.append(a, b, axis=0)
print(new2d)
print(f"new array shape: {new2d.shape}")
```

    [[0]
     [1]
     [2]]

    [[3]
     [4]]

    [[0]
     [1]
     [2]
     [3]
     [4]]
    new array shape: (5, 1)

For example, in the cell above, if you change `axis=0` to `axis=1`,

you are trying to connect the side of `a` with length=3 to the side of `b` with length=2.

There are dedicated functions to simplify joining or merging arrays.

If you are interested to expeirment further with joiing arrays you can try out the following functions:
 - `np.concatenate()` : Joins a sequence of arrays along an existing axis.
 - `np.vstack()` or `np.r_[]`: Stacks arrays row-wise
 - `np.hstack()` : Stacks arrays horizontally
 - `np.column_stack()` or `np.c_[]` : Stacks arrays column-wise
Refer to last week's seminar for how to inpterpret the function documentation.

It can also be useful to remove individual (single or multiple) elements.

For example, the following expand the locations within the array that you can change beyond the location at the *end* of the array.

#### Adding Elements to an Array

```python
# Add items to an array
# The insert() function arguments are
# 1) The array to insert to
# 2) The index of the inserted element
# 3) The value of the inserted element

a = ([1, 2, 3])
a = np.insert(a, 1, 4)
print(a)
```

    [1 4 2 3]

Notice that, again, the output is a 1D aray by default

```python
# Add items to an array

b = np.array([[1, 1],
              [2, 2],
              [3, 3]])

print(f"original array shape: {b.shape}")

b = np.insert(b, 1, [4, 4])

print(b)

print(f"new array shape: {b.shape}")
```

    original array shape: (3, 2)
    [1 4 4 1 2 2 3 3]
    new array shape: (8,)

To preserve the multi-dimensional structure of an array, we can specify the axis on which to insert an element or range of elements.

 In the example below, a column is inserted at element 1 of axis 1.

```python
# Add items to an array

b = np.array([[1, 1],
              [2, 2],
              [3, 3]])

b = np.insert(b, 1, [3, 2, 1], axis=1)
print(b)
```

    [[1 3 1]
     [2 2 2]
     [3 1 3]]

Notice what happens when we insert a *single* value on a specified axis

```python
b = np.insert(b, 1, 4, axis=1)
print(b)
```

    [[1 4 3 1]
     [2 4 2 2]
     [3 4 1 3]]

This behaviour is due to a very useful property called *broadcasting*.

We will study the rules governing broadcasting later in this seminar.

#### Deleting Items from an Array

```python
# Items are deleted from their position in a 1D array by default

z = np.array([1, 3, 4, 5, 6, 7, 8, 9])

z = np.delete(z, 3)
print(z)

z = np.delete(z, [0, 1, 2])
print(z)

```

    [1 3 4 6 7 8 9]
    [6 7 8 9]

```python
# Again, axes to delete can be optionally specified:

z = np.array([[1, 3, 4, 5], [6, 7, 8, 9]])
print(z)
print()

z = np.delete(z, 3, axis=1)
print(z)
print()

z = np.delete(z, [0, 1, 2], axis=1)
print(z)
print()
```

    [[1 3 4 5]
     [6 7 8 9]]

    [[1 3 4]
     [6 7 8]]

    []

#### Changing Items in an Array

```python
c = np.array([1, 2, 3])
c[1] = 4
print(c)
```

    [1 4 3]

### Magic Functions
We can use *magic function* (http://ipython.readthedocs.io/en/stable/interactive/magics.html), `%timeit`, to compare the time the user-defined function takes to execute compared to the Numpy function.

It is important to optimise code for the most desirable parameter. In this example, the user defined code is significantly faster, but the function using numpy applies to a far wider range of input cases.

```python
%timeit max_min_mean(0.5, 0.1, -20)
print()
%timeit np_max_min_mean([0.5, 0.1, -20])
```

##### Try it yourself
In the cell below, find a Numpy function that provides the same solution as the function you wrote as your answer to __Seminar 3, Review Exercise: Indexing, part (A)__:

Add two vectors, $\mathbf{A}$ and $\mathbf{B}$ such that:
$ \mathbf{A} + \mathbf{B} = [(A_1 + B_1),
                              (A_2 + B_2),
                              ...
                              (A_n + B_n)]$

__(A)__ Use the Numpy function to add vectors:

$\mathbf{A} = [-2, 1, 3]$

$\mathbf{B} = [6, 2, 2]$

Check that your answer is the same as your answer to __Seminar 3, Review Exercise: Indexing__.

__(B)__ Using your answer to  __Seminar 3, Review Exercise: Indexing__ write a function `vector_add` that takes vectors A and B as inputs and returns the sum of the two vectors by calling:

```python
vector_add(A, B)
```

__(C)__ Use *magic function* `%timeit`, to compare the speed of the Numpy function to the user defined function `vector_add`.

 Which is fastest?

```python
# Vector Addition
```

## Stacking Functions
If performing multiple functions on a variable or data structure, operations can be stacked to produce shorter code.

```python
a = range(10)
a = list(a)
a = np.cos(a)
a = np.sum(a)
print(a)

a = np.sum(np.cos(list(range(10))))
print(a)
```

## Importing Data from  a .csv File to a Numpy Array
Real data is often stored in .csv files (typically viewed in Excel).

csv files are simply comma separated values (although the values can be separated or *delimited* by other things than commas).

A Numpy array can be a very useful way to store and manipulate data e.g. the raw data from an experiment.

Numerical data can be loaded from a .txt or .data. or .csv file using the function; `numpy.loadtxt`.

The file to be loaded must be in the same directory as your python program.

Otherwise you must specify the the full path to the data file using / to seperate directory names.

The filename (or path plus filename) needs to be between "" quotation marks.

For example the file data.dat in the folder sample_data contains:
>0.000 1.053 2.105 3.158 4.211

74.452 48.348 68.733 59.796 54.123

The default delimiter is a space.

The default data type to output is an array of floating point values.

```python
A = np.loadtxt('sample_data/sample_data.dat')
print(A)
print(type(A))
print(A[0][1])
```

    [[  1.053   2.105   3.158   4.211   6.065]
     [ 48.348  68.733  59.796  54.123  74.452]]
    <class 'numpy.ndarray'>
    2.105

The __delimiter__ should (sometimes) be specified.

This tells Python how to separate the data into the individual elements of an array.

The default delimiter is a space.

(Data separated by spaces will be automatically be assigned different indices).

The __data type__ should (sometimes) be specified.

This tells Python how to separate the data into the individual elements of an array.

The default data type is `float`.

If your data contains items that cannot be expressed as a float, importing it will cause an error unless the data-type is specified.

Mixed data types can be imported as `string` values.

For example, the data in sample_student_data.txt:
- is seperated by tabs (`/t`)
- cannot all be converted to float data (in some columns)

```Python
Subject	Sex	DOB	Height	Weight	BP
(ID)	M/F	dd/mm/yy	m	kg	mmHg
JW-1	M	19/12/1995	1.82	92.4	119/76
JW-2	M	11/01/1996	1.77	80.9	114/73
JW-3	F	02/10/1995	1.68	69.7	124/79
JW-6	M	06/07/1995	1.72	75.5	110/60
JW-7	F	28/03/1996	1.66	72.4	-
JW-9	F	11/12/1995	1.78	82.1	115/75
JW-10	F	07/04/1996	1.6	45	-/-
JW-11	M	22/08/1995	1.72	77.2	97/63
JW-12	M	23/05/1996	1.83	88.9	105/70
JW-14	F	12/01/1996	1.56	56.3	108/72
JW-15	F	01/06/1996	1.64	65	99/67
JW-16	M	10/09/1995	1.63	73	131/84
JW-17	M	17/02/1996	1.67	89.8	101/76
JW-18	M	31/07/1996	1.66	75.1	-/-
JW-19	F	30/10/1995	1.59	67.3	103/69
JW-22	F	09/03/1996	1.7	45	119/80
JW-23	M	15/05/1995	1.97	89.2	124/82
JW-24	F	01/12/1995	1.66	63.8	100/78
JW-25	F	25/10/1995	1.63	64.4	-/-
JW-26	M	17/04/1996	1.69	55	121/82
```

```python
np.loadtxt('sample_data/sample_student_data.txt', delimiter="\t", dtype=str)
```

    array([['Student', 'Sex', 'DOB', 'Height', 'Weight', 'BP'],
           ['(ID)', 'M/F', 'dd/mm/yy', 'm', 'kg', 'mmHg'],
           ['JW-1', 'M', '19/12/1995', '1.82', '92.4', '119/76'],
           ['JW-2', 'M', '11/01/1996', '1.77', '80.9', '114/73'],
           ['JW-3', 'F', '02/10/1995', '1.68', '69.7', '124/79'],
           ['JW-4', 'M', '11/01/1996', '1.77', '80.9', '114/73'],
           ['JW-5', 'F', '02/10/1995', '1.68', '69.7', '124/79'],
           ['JW-6', 'M', '06/07/1995', '1.72', '75.5', '110/60'],
           ['JW-7', 'F', '28/03/1996', '1.66', '72.4', '-'],
           ['JW-9', 'F', '11/12/1995', '1.78', '82.1', '115/75'],
           ['JW-10', 'F', '07/04/1996', '1.6', '45', '-/-'],
           ['JW-11', 'M', '22/08/1995', '1.72', '77.2', '97/63'],
           ['JW-12', 'M', '23/05/1996', '1.83', '88.9', '105/70'],
           ['JW-14', 'F', '12/01/1996', '1.56', '56.3', '108/72'],
           ['JW-15', 'F', '01/06/1996', '1.64', '65', '99/67'],
           ['JW-16', 'M', '10/09/1995', '1.63', '73', '131/84'],
           ['JW-17', 'M', '17/02/1996', '1.67', '89.8', '101/76'],
           ['JW-18', 'M', '31/07/1996', '1.66', '75.1', '-/-'],
           ['JW-19', 'F', '30/10/1995', '1.59', '67.3', '103/69'],
           ['JW-22', 'F', '09/03/1996', '1.7', '45', '119/80'],
           ['JW-23', 'M', '15/05/1995', '1.97', '89.2', '124/82'],
           ['JW-24', 'F', '01/12/1995', '1.66', '63.8', '100/78'],
           ['JW-25', 'F', '25/10/1995', '1.63', '64.4', '-/-'],
           ['JW-26', 'M', '17/04/1996', '1.69', '55', '121/82']],
          dtype='<U10')

Regions can be selected, for example to select only numerical data.

`skiprows` skips the first n lines.

`usecols` specifies which columns to read, with 0 being the first.

For example, usecols = (1,4,5) will extract the 2nd, 5th and 6th columns.

```python
import numpy as np
np.loadtxt('sample_data/sample_student_data.txt', dtype=float, skiprows=9, usecols=(3,4))
```

    array([[  1.78,  82.1 ],
           [  1.6 ,  45.  ],
           [  1.72,  77.2 ],
           [  1.83,  88.9 ],
           [  1.56,  56.3 ],
           [  1.64,  65.  ],
           [  1.63,  73.  ],
           [  1.67,  89.8 ],
           [  1.66,  75.1 ],
           [  1.59,  67.3 ],
           [  1.7 ,  45.  ],
           [  1.97,  89.2 ],
           [  1.66,  63.8 ],
           [  1.63,  64.4 ],
           [  1.69,  55.  ]])

You can now use the imported data as a regular Numpy array.

# Summary

- Python has an extensive __standard library__ of built-in functions.
- More specialised libraries of functions and constants are available. We call these __packages__.
- Packages are imported using the keyword `import`
- The function documentation tells is what it does and how to use it.
- When calling a library function it must be prefixed with a __namespace__ is used to show from which package it should be called.
- The magic function `%timeit` can be used to time the execution of a function.

 - A data structure is used to assign a collection of values to a single collection name.
 - A Python list can store multiple items of data in sequentially numbered elements (numbering starts at zero)
 - Data stored in a list element can be referenced using the list name can be referenced using the list name followed by an index number in [] square brackets.
 - The `len()` function returns the length of a specified list.

# Test-Yourself Exercises

Compete the Test-Youself exercises below.

Save your answers as .py files and email them to:

philamore.hemma.5s@kyoto-u.ac.jp

## Test-Yourself Exercise : The Dot Product
__(A)__

Earlier, we used the geometric representation of the dot product:

$\mathbf{A} \cdot \mathbf{B} \sum_{i=1}^n A_i B_i = A_x B_x + A_y B_y + A_z B_z$

to find the sum of two vectors:

$ \mathbf{A} = [A_x, A_y, A_z]$

$ \mathbf{B} = [B_x, B_y, B_z]$

Numpy has a dedicated function to compute the dot product.

Search for the function online and use it to compute the dot product of two 3D position vectors, expressed as lists `C` and `D`.

```python
# Find the dot product of C and D
C = [-1, 2, 6]
D = [4, 3, 3]

```

```python
# Find the dot product of C and D
# Example Solution

import numpy as np
C = [-1, 2, 6]
D = [4, 3, 3]

np.dot(C, D)
```

The dot product has an alternative representation:

__GEOMETRIC REPRESENTATION OF THE DOT PRODUCT__

\begin{align}
\mathbf{A} \cdot \mathbf{B} = |\mathbf{A}| |\mathbf{B}| cos(\theta)
\end{align}

Where:

$\theta$ is the angle between the two vectors.

$|\mathbf{A}|$ and $|\mathbf{B}|$ are the *magnitudes* of $\mathbf{A}$ and $\mathbf{B}$

The magnitude of an $n$-length vector $ \mathbf{A} = [A_1, ..., A_n]$ is:

$|\mathbf{A}| = \sqrt{A_1^2 + ... + A_n^2}$

e.g.

The magnitude of __2D position__ vector, $\mathbf{r} = [x, y]$:

$|\mathbf{r}| = \sqrt{x^2 + y^2}$

The magnitude of __3D position__ vector, $\mathbf{r} = [x, y, z]$:

$|\mathbf{r}| = \sqrt{x^2 + y^2 + z^2}$

__(B)__

If the dot product is known, the angle between two vectors can be found (from its cosine).

Search online to find a numpy function that computes *magnitude*.

Search online to find a numpy function for the *inverse cosine*.

Find the angle between the vectors expressed as lists `C` and `D` in part __(A)__.

```python
# Find the angle between C and D
C = [-1, 2, 6]
D = [4, 3, 3]

```

```python
# Find the angle between C and D
# Example Solution

C = [-1, 2, 6]
D = [4, 3, 3]

dotCD = np.dot(C, D)
magC = np.linalg.norm(C)
magD = np.linalg.norm(D)

theta =  np.arccos(dotCD / (magC * magD))

print(theta)
```

__(C)__

The dot product also indicates if the angle between two vectors is:
   - acute ($\mathbf{C} \cdot \mathbf{D}>0$)
   - obtuse ($\mathbf{C} \cdot \mathbf{D}<0$)
   - right angle ($\mathbf{C} \cdot \mathbf{D}==0$)

Using `if`, `elif` and `else`, classify the angle between `C` and `D` as acute, obtuse or right angle.

```python
# Angle between C and D is obtuse, acute or right angle?

C = [-1, 2, 6]
D = [4, 3, 3]

```

```python
# Angle between C and D is obtuse, acute or right angle?
# Example Solution

C = [-1, 2, 6]
D = [4, 3, 3]

dotCD = np.dot(C, D)

if dotCD > 0:
    print("theta is acute")
elif dotCD < 0:
    print("theta is obtuse")
else:
    print("theta is right angle")
```

## Test-Yourself Exercise : Importing .csv Data and Working with Arrays
__(A) Importing Data__

We can work with data stored in .csv files by importing it to a numpy array.

The file `douglas_data.csv` contains a data set of recorded parameters for a sample of wooden beams.

Import `douglas_data.csv` from the `sample_data` folder in the `ILAS_python_for_engineers` directory.

To import the data without errors, first try setting:
 - delimiter = `"\t"` (tab)
 - data type = `str` (string)

__*What are the delimiters used in the data?*__

__*Which rows and columns contain non-numerical data?*__

Now import the data as a Numpy array of floating point values.

Exclude the rows and columns containing non-numeric data.

*Remember : The use of scientific notation can be surpressed by:*

        np.set_printoptions(suppress=True)

```python
# Importing Data
```

```python
# Importing Data
# Example Solution

import numpy as np

A = np.loadtxt('sample_data/douglas_data.csv', delimiter="\t", dtype=str)

print(A)

A = np.loadtxt('sample_data/douglas_data.csv', delimiter=",", dtype=float, skiprows=2, usecols=(1,2,3,4,5,6,7,8))

print(A)
```

__(B) Manipulating Data__

Select the first 10 rows of the array to create a new array.

The column furthest to the right is the bending strength (`bstrength`), measured in units of $\mathrm{N/mm}^2$.

Convert the data in this column to units $\mathrm{N/m}^2$.

Each beam has the same cross sectional area = $100\mathrm{cm}^2$.

Add a new column to the array that contains mass of the beam (kg) using the columns `density` and `beamheight`.

```python
# Manipulating Data
```

```python
# Manipulating Data
# Example Solution

B = A[:10, :]

B[:,7] *= 1000000
B[:,-1] *= 1000000

area = 0.01 # m2

height = B[:,5] /100 # m

density = B[:,4]  #kg/m3

mass = area * density * height

# Insert column, mass, at horizontal position -1
B = np.insert(B, 0, mass, axis=1)

print(B)
```

    [[6.8175e+00 1.3300e+01 4.0000e-02 3.0000e+00 1.4053e+04 6.7500e+02
      1.0100e+02 1.5452e+04 5.8400e+25]
     [4.7400e+00 1.2000e+01 1.6000e-01 2.5000e+00 2.0611e+04 4.7400e+02
      1.0000e+02 1.7272e+04 7.4350e+25]
     [5.9004e+00 1.2800e+01 1.4000e-01 3.8800e+00 1.8846e+04 5.9600e+02
      9.9000e+01 1.8456e+04 4.9820e+25]
     [5.8200e+00 1.1700e+01 1.3000e-01 2.0200e+00 1.8587e+04 5.8200e+02
      1.0000e+02 1.8940e+04 7.8520e+25]
     [6.7800e+00 1.2000e+01 1.6000e-01 2.1300e+00 1.9299e+04 6.7800e+02
      1.0000e+02 1.6864e+04 7.9310e+25]
     [5.9500e+00 1.2400e+01 4.0000e-02 2.9800e+00 2.1695e+04 5.9500e+02
      1.0000e+02 1.9440e+04 6.4340e+25]
     [5.9200e+00 1.2500e+01 3.2000e-01 3.6700e+00 1.6523e+04 5.9200e+02
      1.0000e+02 1.6152e+04 5.8190e+25]
     [6.4034e+00 1.1500e+01 7.0000e-02 3.6700e+00 1.8333e+04 6.3400e+02
      1.0100e+02 1.8480e+04 8.8390e+25]
     [5.9792e+00 1.3100e+01 1.9000e-01 2.4400e+00 1.8628e+04 5.9200e+02
      1.0100e+02 1.4604e+04 3.3020e+25]
     [5.4540e+00 1.1700e+01 2.5000e-01 3.0000e+00 1.5683e+04 5.4000e+02
      1.0100e+02 1.6628e+04 6.0280e+25]]

__(C) Displaying Data__

Print the mass of the 1st beam in the array.

Print a string to indicate what this value means e.g.

       The mass of beam 1 is ...

Print the data in odd numbered columns of row 5.

```python
# Displaying Data
```

    The mass of beam 1 is {B[0,0]} kg

```python
# Displaying Data
# Example Solution

print("The mass of beam 1 is {B[0,0]} kg")
print(B[5,1::2])
```

    The mass of beam 1 is {B[0,0]} kg
    [1.240e+01 2.980e+00 5.950e+02 1.944e+04]

# Review Exercises
Here are a series of short problems for you to practise each of the new Python skills that you have learnt today.

### Review Exercise: Numpy Package Functions.
Find a function in the Python Numpy documentation that matches the function definition and use it to solve the problems below:

__(A)__ Definition: *Calculates the exponential function, $y= e^x$ for all elements in the input array.*

Print a list where each element is the exponential function of the corresponding element in list `a = [0.1, 0, 10]`

```python
# Print a list where each element is the exponential of the corresponding element in list a
```

__(B)__ Definition: *Converts angles from degrees to radians.*

Convert angle `theta`, expressed in degrees, to radians:

`theta` = 47

```python
# convert angle `theta`, expressed in degrees, to radians
```

__(C)__ Definition: *Return the positive square-root of an array, element-wise.*

Print a list where each element is the square root of the corresponding element in list `a = [4, 16, 81]`

```python
# Print a list where each element is the square root of the corresponding element in list a
```

### Review Exercise:  Data structures.

__(A)__ In the cell below, use Python to identify the type of data structure, C?

__(B)__ Write a line of code that checks whether 3 exists within the data structure.

__(C)__ Write a line of code that checks whether 3.0 exists within the data structure.

__(D)__ Write a line of code that checks whether "3" exists within the data structure.

```python
C = [2, 3, 5, 6, 1, "hello"]
```

### Review Exercise:  Using a single list with a `for` loop.
In the cell below, use a `for` loop to print the first letter of each month in the list.

Jump to Indexing for how to pick out individual letters of a string.

Jump to Iterating over lists for how to loop through each element of a list.

```python
# Print the first letter of each month in the list

months = ["January",
         "February",
         "March",
         "April",
         "May",
         "June",
         "July",
         "August",
         "September",
         "October",
         "November",
         "December"]
```

```python

```


## Qiskit installation and test

- [Check your system](#check)
- [Install qiskit](#install)
- [Tips](#tips)
- [Execute an example quantum program](#test)

### Check your system

Check your system, if Qiskit has already been installed:

```python
import qiskit
versions = qiskit.__qiskit_version__
print("The version of Qiskit is",versions['qiskit'])
print()
print("The version of each component:")
for key in versions:
    print(key,"->",versions[key])
```

    The version of Qiskit is 0.23.0

    The version of each component:
    qiskit-terra -> 0.16.0
    qiskit-aer -> 0.7.0
    qiskit-ignis -> 0.5.0
    qiskit-ibmq-provider -> 0.11.0
    qiskit-aqua -> 0.8.0
    qiskit -> 0.23.0

**You should be able to see the version number of any library that is already installed in your system.**

### Install qiskit

(If you are an experienced user, visit this link: https://qiskit.org/documentation/install.html)

You can install Qiskit by executing the following cell:

```python
!pip install qiskit[visualization] --user
```

    Requirement already satisfied: qiskit[visualization] in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (0.23.0)
    Requirement already satisfied: qiskit-aer==0.7.0 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from qiskit[visualization]) (0.7.0)
    Requirement already satisfied: qiskit-ignis==0.5.0 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from qiskit[visualization]) (0.5.0)
    Requirement already satisfied: qiskit-terra==0.16.0 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from qiskit[visualization]) (0.16.0)
    Requirement already satisfied: qiskit-aqua==0.8.0 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from qiskit[visualization]) (0.8.0)
    Requirement already satisfied: qiskit-ibmq-provider==0.11.0 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from qiskit[visualization]) (0.11.0)
    Requirement already satisfied: pydot; extra == "visualization" in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from qiskit[visualization]) (1.4.1)
    Requirement already satisfied: ipywidgets>=7.3.0; extra == "visualization" in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from qiskit[visualization]) (7.5.1)
    Requirement already satisfied: seaborn>=0.9.0; extra == "visualization" in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from qiskit[visualization]) (0.10.1)
    Requirement already satisfied: matplotlib>=2.1; extra == "visualization" in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from qiskit[visualization]) (3.1.3)
    Requirement already satisfied: pylatexenc>=1.4; extra == "visualization" in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from qiskit[visualization]) (2.8)
    Requirement already satisfied: pygments>=2.4; extra == "visualization" in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from qiskit[visualization]) (2.6.1)
    Requirement already satisfied: pillow>=4.2.1; extra == "visualization" in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from qiskit[visualization]) (7.1.2)
    Requirement already satisfied: numpy>=1.16.3 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from qiskit-aer==0.7.0->qiskit[visualization]) (1.18.1)
    Requirement already satisfied: cython>=0.27.1 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from qiskit-aer==0.7.0->qiskit[visualization]) (0.29.17)
    Requirement already satisfied: pybind11>=2.4 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from qiskit-aer==0.7.0->qiskit[visualization]) (2.6.0)
    Requirement already satisfied: scipy>=1.0 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from qiskit-aer==0.7.0->qiskit[visualization]) (1.4.1)
    Requirement already satisfied: setuptools>=40.1.0 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from qiskit-ignis==0.5.0->qiskit[visualization]) (47.1.1.post20200604)
    Requirement already satisfied: scikit-learn>=0.17 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from qiskit-ignis==0.5.0->qiskit[visualization]) (0.22.1)
    Requirement already satisfied: networkx>=2.2 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from qiskit-terra==0.16.0->qiskit[visualization]) (2.4)
    Requirement already satisfied: jsonschema>=2.6 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from qiskit-terra==0.16.0->qiskit[visualization]) (3.2.0)
    Requirement already satisfied: python-constraint>=1.4 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from qiskit-terra==0.16.0->qiskit[visualization]) (1.4.0)
    Requirement already satisfied: fastjsonschema>=2.10 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from qiskit-terra==0.16.0->qiskit[visualization]) (2.14.5)
    Requirement already satisfied: dill>=0.3 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from qiskit-terra==0.16.0->qiskit[visualization]) (0.3.3)
    Requirement already satisfied: sympy>=1.3 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from qiskit-terra==0.16.0->qiskit[visualization]) (1.5.1)
    Requirement already satisfied: python-dateutil>=2.8.0 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from qiskit-terra==0.16.0->qiskit[visualization]) (2.8.1)
    Requirement already satisfied: psutil>=5 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from qiskit-terra==0.16.0->qiskit[visualization]) (5.7.0)
    Requirement already satisfied: ply>=3.10 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from qiskit-terra==0.16.0->qiskit[visualization]) (3.11)
    Requirement already satisfied: retworkx>=0.5.0 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from qiskit-terra==0.16.0->qiskit[visualization]) (0.5.0)
    Requirement already satisfied: yfinance in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from qiskit-aqua==0.8.0->qiskit[visualization]) (0.1.55)
    Requirement already satisfied: pandas in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from qiskit-aqua==0.8.0->qiskit[visualization]) (1.0.3)
    Requirement already satisfied: docplex in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from qiskit-aqua==0.8.0->qiskit[visualization]) (2.16.196)
    Requirement already satisfied: dlx in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from qiskit-aqua==0.8.0->qiskit[visualization]) (1.0.4)
    Requirement already satisfied: h5py in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from qiskit-aqua==0.8.0->qiskit[visualization]) (2.10.0)
    Requirement already satisfied: quandl in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from qiskit-aqua==0.8.0->qiskit[visualization]) (3.5.3)
    Requirement already satisfied: fastdtw in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from qiskit-aqua==0.8.0->qiskit[visualization]) (0.3.4)
    Requirement already satisfied: requests>=2.19 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from qiskit-ibmq-provider==0.11.0->qiskit[visualization]) (2.23.0)
    Requirement already satisfied: nest-asyncio!=1.1.0,>=1.0.0 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from qiskit-ibmq-provider==0.11.0->qiskit[visualization]) (1.4.2)
    Requirement already satisfied: websockets>=8 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from qiskit-ibmq-provider==0.11.0->qiskit[visualization]) (8.1)
    Requirement already satisfied: urllib3>=1.21.1 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from qiskit-ibmq-provider==0.11.0->qiskit[visualization]) (1.25.8)
    Requirement already satisfied: requests-ntlm>=1.1.0 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from qiskit-ibmq-provider==0.11.0->qiskit[visualization]) (1.1.0)
    Requirement already satisfied: pyparsing>=2.1.4 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from pydot; extra == "visualization"->qiskit[visualization]) (2.4.7)
    Requirement already satisfied: nbformat>=4.2.0 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (5.0.6)
    Requirement already satisfied: traitlets>=4.3.1 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (4.3.3)
    Requirement already satisfied: ipykernel>=4.5.1 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (5.1.4)
    Requirement already satisfied: widgetsnbextension~=3.5.0 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (3.5.1)
    Requirement already satisfied: ipython>=4.0.0; python_version >= "3.3" in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (7.13.0)
    Requirement already satisfied: kiwisolver>=1.0.1 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from matplotlib>=2.1; extra == "visualization"->qiskit[visualization]) (1.2.0)
    Requirement already satisfied: cycler>=0.10 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from matplotlib>=2.1; extra == "visualization"->qiskit[visualization]) (0.10.0)
    Requirement already satisfied: joblib>=0.11 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from scikit-learn>=0.17->qiskit-ignis==0.5.0->qiskit[visualization]) (0.15.1)
    Requirement already satisfied: decorator>=4.3.0 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from networkx>=2.2->qiskit-terra==0.16.0->qiskit[visualization]) (4.4.2)
    Requirement already satisfied: attrs>=17.4.0 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from jsonschema>=2.6->qiskit-terra==0.16.0->qiskit[visualization]) (19.3.0)
    Requirement already satisfied: six>=1.11.0 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from jsonschema>=2.6->qiskit-terra==0.16.0->qiskit[visualization]) (1.15.0)
    Requirement already satisfied: pyrsistent>=0.14.0 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from jsonschema>=2.6->qiskit-terra==0.16.0->qiskit[visualization]) (0.16.0)
    Requirement already satisfied: mpmath>=0.19 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from sympy>=1.3->qiskit-terra==0.16.0->qiskit[visualization]) (1.1.0)
    Requirement already satisfied: lxml>=4.5.1 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from yfinance->qiskit-aqua==0.8.0->qiskit[visualization]) (4.5.1)
    Requirement already satisfied: multitasking>=0.0.7 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from yfinance->qiskit-aqua==0.8.0->qiskit[visualization]) (0.0.9)
    Requirement already satisfied: pytz>=2017.2 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from pandas->qiskit-aqua==0.8.0->qiskit[visualization]) (2020.1)
    Requirement already satisfied: more-itertools in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from quandl->qiskit-aqua==0.8.0->qiskit[visualization]) (8.3.0)
    Requirement already satisfied: inflection>=0.3.1 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from quandl->qiskit-aqua==0.8.0->qiskit[visualization]) (0.5.1)
    Requirement already satisfied: chardet<4,>=3.0.2 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from requests>=2.19->qiskit-ibmq-provider==0.11.0->qiskit[visualization]) (3.0.4)
    Requirement already satisfied: certifi>=2017.4.17 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from requests>=2.19->qiskit-ibmq-provider==0.11.0->qiskit[visualization]) (2020.4.5.1)
    Requirement already satisfied: idna<3,>=2.5 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from requests>=2.19->qiskit-ibmq-provider==0.11.0->qiskit[visualization]) (2.9)
    Requirement already satisfied: ntlm-auth>=1.0.2 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from requests-ntlm>=1.1.0->qiskit-ibmq-provider==0.11.0->qiskit[visualization]) (1.5.0)
    Requirement already satisfied: cryptography>=1.3 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from requests-ntlm>=1.1.0->qiskit-ibmq-provider==0.11.0->qiskit[visualization]) (2.9.2)
    Requirement already satisfied: ipython-genutils in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from nbformat>=4.2.0->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (0.2.0)
    Requirement already satisfied: jupyter-core in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from nbformat>=4.2.0->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (4.6.3)
    Requirement already satisfied: tornado>=4.2 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from ipykernel>=4.5.1->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (6.0.4)
    Requirement already satisfied: jupyter-client in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from ipykernel>=4.5.1->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (6.1.3)
    Requirement already satisfied: notebook>=4.4.1 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from widgetsnbextension~=3.5.0->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (6.0.3)
    Requirement already satisfied: jedi>=0.10 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from ipython>=4.0.0; python_version >= "3.3"->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (0.15.2)
    Requirement already satisfied: pickleshare in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from ipython>=4.0.0; python_version >= "3.3"->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (0.7.5)
    Requirement already satisfied: prompt-toolkit!=3.0.0,!=3.0.1,<3.1.0,>=2.0.0 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from ipython>=4.0.0; python_version >= "3.3"->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (3.0.5)
    Requirement already satisfied: backcall in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from ipython>=4.0.0; python_version >= "3.3"->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (0.1.0)
    Requirement already satisfied: colorama; sys_platform == "win32" in c:\users\aarbs\appdata\roaming\python\python38\site-packages (from ipython>=4.0.0; python_version >= "3.3"->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (0.4.3)
    Requirement already satisfied: cffi!=1.11.3,>=1.8 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from cryptography>=1.3->requests-ntlm>=1.1.0->qiskit-ibmq-provider==0.11.0->qiskit[visualization]) (1.14.0)
    Requirement already satisfied: pywin32>=1.0; sys_platform == "win32" in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from jupyter-core->nbformat>=4.2.0->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (227)
    Requirement already satisfied: pyzmq>=13 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from jupyter-client->ipykernel>=4.5.1->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (18.1.1)
    Requirement already satisfied: terminado>=0.8.1 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (0.8.3)
    Requirement already satisfied: nbconvert in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (5.6.1)
    Requirement already satisfied: jinja2 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (2.11.2)
    Requirement already satisfied: Send2Trash in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (1.5.0)
    Requirement already satisfied: prometheus-client in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (0.7.1)
    Requirement already satisfied: parso>=0.5.2 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from jedi>=0.10->ipython>=4.0.0; python_version >= "3.3"->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (0.5.2)
    Requirement already satisfied: wcwidth in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from prompt-toolkit!=3.0.0,!=3.0.1,<3.1.0,>=2.0.0->ipython>=4.0.0; python_version >= "3.3"->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (0.1.9)
    Requirement already satisfied: pycparser in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from cffi!=1.11.3,>=1.8->cryptography>=1.3->requests-ntlm>=1.1.0->qiskit-ibmq-provider==0.11.0->qiskit[visualization]) (2.20)
    Requirement already satisfied: bleach in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (3.1.4)
    Requirement already satisfied: pandocfilters>=1.4.1 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (1.4.2)
    Requirement already satisfied: mistune<2,>=0.8.1 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (0.8.4)
    Requirement already satisfied: testpath in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (0.4.4)
    Requirement already satisfied: defusedxml in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (0.6.0)
    Requirement already satisfied: entrypoints>=0.2.2 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (0.3)
    Requirement already satisfied: MarkupSafe>=0.23 in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from jinja2->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (1.1.1)
    Requirement already satisfied: webencodings in c:\users\aarbs\anaconda3\envs\virtual_environment_name\lib\site-packages (from bleach->nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.3.0; extra == "visualization"->qiskit[visualization]) (0.5.1)

__*Restart the kernel*__ (check "Kernel" menu) to apply the changes to the current notebook.

### Tips

_Any terminal/shell command can be executed in the notebook cells by putting exclamation mark (!) to the beginning of the command._

_$\rightarrow$ For updating Qiskit version, execute the following command on a code cell_

    !pip install -U qiskit --user

_$\rightarrow$ For uninstall Qiskit, execute the following command on a code cell_

    !pip uninstall qiskit

```python
#!pip install -U qiskit --user
#!pip uninstall qiskit
```

### Execute an example quantum program

1) Create a quantum circuit

```python
# import the objects from qiskit
from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit, execute, Aer
from random import randrange

# create a quantum circuit and its register objects
qreg = QuantumRegister(2) # quantum register with two quantum bits
creg = ClassicalRegister(2) # classical register with two classical bit
circuit = QuantumCircuit(qreg,creg) # quantum circuit composed by a quantum register and a classical register

# apply a Hadamard gate to the first qubit
circuit.h(qreg[0])

# set the second qubit to state |1>
circuit.x(qreg[1])

# apply CNOT(first_qubit,second_qubit)
circuit.cx(qreg[0],qreg[1])

# measure the both qubits
circuit.measure(qreg,creg)

print("The execution of the cell was completed, and the circuit was created :)")
```

    The execution of the cell was completed, and the circuit was created :)

2) Draw the circuit

_Run the cell once more if the figure is not shown_

```python
# draw circuit
circuit.draw(output='mpl')

# the output will be a "matplotlib.Figure" object
```

3) Execute the circuit 1024 times in the local simulator and print the observed the outcomes

```python
## execute the circuit 1024 times
job = execute(circuit,Aer.get_backend('qasm_simulator'),shots=1024)
# get the result
counts = job.result().get_counts(circuit)
print(counts)
```

    {'01': 493, '10': 531}

```python

```

QWorld's Global Quantum Programming Workshop/Qiskit Basics/1. Installing Qiskit + Check Version.ipynb