#import (library) as (give the library a nickname/alias)
import numpy as np
import pandas as pd

"""
Explanation: 2.1 Series
Content:
  - 2.1.0 Series Structure
  - 2.1.1 Basic Operations
  - 2.1.2 Indexing and Selecting Data
  - 2.1.3 Boolean Masking
  - 2.1.4 Missing Values
  - 2.1.5 Exercises
Python has abundant libraries for various purposes like scientific computing, data analysis, data visualisation and machine learning. In this course, we'll be using numpy for numerical computation, pandas for data preparation, matplotlib for visualisation and scipy.stats for statistical analysis.
End of explanation
"""


# Create a series from a list
s = pd.Series([2,3,5,7,11])
# Display the series
s

# Get the series values 
s.values

# Get the series index
s.index

# Name the index
s0 = pd.Series([2,3,5,7,11], index=['a','b','c','d','e'])
s0

# Size/Length of series
s.size # same answer as len(s)

# Sort values
s.sort_values(ascending=False)

"""
Explanation: 2.1.0 Series Structure
Series is a one-dimensional array capable of holding any data type, like int, str, float,....
It is similar to list in Python, but each element is lablled with an index.
End of explanation
"""


s + s

s * 2

s **2

np.exp(s)

"""
Explanation: 2.1.1 Basic Operations
Series allows us to use "vectorised operations". This means that arithmetic operations can be performed without the use of a for loop to iterate through elements of the Series.
End of explanation
"""


# Sum
s.sum() # same answer as sum(s)

# Average
s.mean() # same asnwer as np.mean(s)

# Std Dev
s.std()

# Median / Q1 / Q3
s.median() # same as s.quantile(0.5)

# Max/Min value
s.max()

# Which index has the max/min value?
s.idxmax()

"""
Explanation: Everyday statistical functions are available as method calls to the Series object.
End of explanation
"""


# Select the first value
s.iloc[0]

# Select the last value
s.iloc[-1]

# Select a contiguous subset of values
s.iloc[1:4]

# Select the first 3 values
s.iloc[:3]

"""
Explanation: 2.1.2 Indexing and Selecting Data using .iloc and .loc
How do we access specific elements in the series? There are two ways, and integer positionals method using the iloc method and a loc method which selects elements by their indexing label. 
.iloc is integer location based selection (from 0, 1, 2 to length-1 of the axis)
End of explanation
"""


# Select value based on index label
s0.loc['b'] # return same asnwer as s0.iloc[1] or s0['b']

# Select a list of values based on labels
s0.loc[['a','c','e']] # return same answer as s0.iloc[[0,2,4]] or s0[['a','c','e']]

"""
Explanation: .loc is labelled based selection.
End of explanation
"""


# Which values are < 10?
s < 10

# Select values based on boolean mask
s[s<10]

# How many terms are <10? 
len(s[s<10])

# Select values which are 2 < x < 10.
s[(s>2) & (s<10)]

# Note that the following is not valid

s[2<s<10]

"""
Explanation: 2.1.3 Conditional Selection
How do we  select elements of a series based on some criteria. For example, select all elements in s which are less than 10.
End of explanation
"""


# Create a new series
s1 = pd.Series([13,np.nan,19])
# Append new series
s2 = s.append(s1)
# observe the indices
s2

# Append new series, ignoring the index
s2 = s.append(s1,ignore_index=True)
s2

# Check for missing value
s2.isnull()

# How many missing values are there?
s2.isnull().sum()

# Drop missing values
s2.dropna()

# Replace missing values
s2.fillna(17)

# Replace a value by another value
# Eg: Replace 2 by 'even prime'
s2.replace(2,'even prime')

"""
Explanation: 2.1.4 Missing Values
NaN (Not a Number) is the standard missing marker used in Pandas.
End of explanation
"""


# Answer should return a series object
names = ['Alvin','Ben','Carl','Danny','Ella','Fang','Gil','Han','Irene','Jane','Ken','Lim','Mark','Ng','Ong','Peng','Quek','Roy','Sam']
marks = [48,62,66,26,72,74,72,55,70,80,62,66,'TX',93,65,30,75,58,51]
t = pd.Series(marks, index = names)

"""
Explanation: 2.1.5 Exercises
Q1: Create a new series t where the values are marks and the indices are names.

names  ['Alvin','Ben','Carl','Danny','Ella','Fang','Gil','Han','Irene','Jane','Ken','Lim','Mark','Ng','Ong','Peng','Quek','Roy','Sam']
marks  [48,62,66,26,72,74,72,55,70,80,62,66,'TX',93,65,30,75,58,51]
End of explanation
"""


# Answer should return a number.
len(t)

"""
Explanation: Q2: How many students are there in this class?
End of explanation
"""


# t.mean() will return an error message

"""
Explanation: Q3(a): What is the average score of the test?
End of explanation
"""


# Answer should return a number
t1 = t.replace('TX',np.nan)
t1.mean()

"""
Explanation: Q3(b): What should you replace 'TX' with? Then, answer Q3(a) again.
End of explanation
"""


# Answer should return a string (name)
t1.idxmax()

"""
Explanation: Q4: Who scored the highest mark?
End of explanation
"""


# Answer should return a list of names
t1[t1 < 50].index

"""
Explanation: Q5: Who failed the test (<50)?
End of explanation
"""


# Answer should return a number correct to 2 d.p.
x = t1[t1>=75]
round(100*len(x)/18,2)

"""
Explanation: Q6: What is the percentage of B+ and above (>=75 / number of students who took MST)? Leave your answer correct to 2 d.p.
End of explanation
"""


