"""
# <center>Ditching Excel for Python</center>
"""
"""
After spending almost a decade with my first love Excel, its time to move on and search for a better half who in thick and thin of my daily tasks is with me and is much better and faster and who can give me a cutting edge in the challenging technological times where new technology is getting ditched by something new at a very rapid pace.
The idea is to replicate almost all excel functionalities in Python, be it using a simple filter or a complex task of creating an array of data from the rows and crunching them to get fancy results
"""
"""
The approach followed here is to start from simple tasks and move to complex computational tasks.
I've tried and designed it such a way that this can be used a universal notebook and you just just have to change the input file and you can get the same result.
However I will encourage you to please replicate the steps yourself for your better understanding.
"""
"""
The inspiration to create something like this came from the non-availablity of a free tutorial which literally gives all. I heavily read and follow Python documentation and you will find a lot of inspiration from that site.
"""
"""
_Our Input and Ouput is both an Excel file :)_
"""
"""
<pre>------------------------------------------------------------------------------------------</pre>
"""
"""
## Importing Excel Files into a Pandas DataFrame
"""
"""
Initial step is to import excel files into dtaframe so we can perform all our tasks on it.
<br>I will be demonstrating the __read_excel__ method of Pandas which supports __xls__ and __xlsx__ file extensions.
<br>__read_csv__ is same as using read_excel, we wont go in depth but I will share an example.
"""
"""
Though __read_excel__ method includes million arguments but I will make you familiarise with the most common ones that will come very handy in day to day operations
"""
"""
I'll be using the Iris sample dataset which is freely available online for educational purpose.
<br>Please follow the below link to download the dataset and ensure to save it in the same folder where you are saving your python file
"""
"""
https://archive.ics.uci.edu/ml/datasets/iris
"""
"""
## The first step is to import necessary libraries in Python
"""
import pandas as pd
import numpy as np
"""
We can import the spreadsheet data into Python using the following code:
"""
"""
pandas.read_excel(io, sheet_name=0, header=0, names=None, index_col=None, parse_cols=None, usecols=None, squeeze=False, dtype=None, engine=None, converters=None, true_values=None, false_values=None, skiprows=None, nrows=None, na_values=None, keep_default_na=True, verbose=False, parse_dates=False, date_parser=None, thousands=None, comment=None, skip_footer=0, skipfooter=0, convert_float=True, mangle_dupe_cols=True, **kwds)
"""
"""
Since there's a plethora of arguments available, lets look at the most used one's.
"""
"""
### Important Pandas read_excel Options
"""
"""
|	Argument	|	Description	|
|		-|		|
|	io	|	A string containing the pathname of the given Excel file.	|
|	sheet_name	|	The Excel sheet name, or sheet number, of the data you want to import. The sheet number can be an integer where 0 is the first sheet, 1 is the second, etc. If a list of sheet names/numbers are given, then the output will be a dictionary of DataFrames. The default is to read all the sheets and output a dictionary of DataFrames.	|
|	header	|	Row number to use for the list of column labels. The default is 0, indicating that the first row is assumed to contain the column labels. If the data does not have a row of column labels, None should be used.	|
|	names	|	A separate Python list input of column names. This option is None by default. This option is the equivalent of assigning a list of column names to the columns attribute of the output DataFrame.	|
|	index_col	|	Specifies which column should be used for row indices. The default option is None, meaning that all columns are included in the data, and a range of numbers is used as the row indices.	|
|	usecols	|	An integer, list of integers, or string that specifies the columns to be imported into the DataFrame. The default is to import all columns. If a string is given, then Pandas uses the standard Excel format to select columns (e.g. "A:C,F,G" will import columns A, B, C, F, and G).	|
|	skiprows	|	The number of rows to skip at the top of the Excel sheet. Default is 0. This option is useful for skipping rows in Excel that contain explanatory information about the data below it.	|

"""
"""
If we are using the path for our local file by default its separated by "\" however python accepts "/", 
so make to change the slashes or simply add the file in the same folder where your python file is.
Should you require detailed explanation on the above, refer to the below medium article.
https://medium.com/@ageitgey/python-3-quick-tip-the-easy-way-to-deal-with-file-paths-on-windows-mac-and-linux-11a072b58d5f
"""
"""
We can use Python to scan files in a directory and pick out the ones we want.
"""
wkbks = glob(os.path.join(os.pardir, 'input', 'xlsx_files_all', 'Ir*.xls'))
sorted(wkbks)
filename = 'Iris.xlsx'
df = pd.read_excel(filename)
print(df)
"""
## Import a specifc sheet
"""
"""
By default, the first sheet in the file is imported to the dataframe as it is.
<br>Using the sheet_name argument we can explicitly mention the sheet that we want to import. Defuat value is 0 i.e. teh first sheet in the file.
    <br>We can either mention the name of the sheet(s) or pass an integer value to refer to the index of the sheet
"""
df1 = pd.read_excel(filename,sheet_name='Sheet2')
print(df1)
"""
## Using a column from the sheet as an Index
"""
"""
Unless explicitly mentioned, an index column is added to the dataframe which by default starts from a 0.
<br>Using the index_col argumement we can manipulate the index column in our dataframe, if we set the value 0 from none, it will use the first column as our index.
"""
df = pd.read_excel(filename,sheet_name='Sheet1', index_col=0)
print(df)
"""
## Skip rows and columns
"""
"""
The default read_excel parameters assumes that the first row is a list of column names, which is incorporated automatically as column labels within the DataFrame.
<br>Using the arguments like skiprows and header we can manipulate the behaviour of the imported dataframe
"""
df = pd.read_excel(filename, sheet_name='Sheet1', header=None, skiprows=1, index_col=0)
print(df)
"""
## Import a specifc column(s)
"""
"""
Using the usecols argument we can specify if we have import a specific column in our dataframe
"""
df = pd.read_excel(filename, sheet_name='Sheet1', header=None, skiprows=1, usecols='B,D')
print(df)
df = pd.read_excel(filename)
#Importing the file again to the dataframe in the original shape to use it for further analysis
"""
_Its not the end of the features available however its a start and you can play around with them as per your requirements_
"""
"""
<pre>------------------------------------------------------------------------------------------</pre>
"""
"""
## Lets have a look at the data from 10,000 feet
"""
"""
As now we have our dataframe, lets look at the data from multiple angles just to get a hang of it/
<br>Pandas have plenty of functions available that we can use. We'll use some of them to have a glimpse of our dataset.
"""
"""
## "Head" to "Tail": 
To view the first or last __five__ rows.
<br>_Default is five, however the argument allows us to use a specific number_
"""
df.head(10)
df.tail()
"""
## View specific column's data
"""
df['SepalLength'].head()
"""
## Getting the name of all columns
"""
df.columns
"""
## Info Method
Gives a summary of Dataframe
"""
df.info()
"""
## Shape Method
Returns the dimensions of Dataframe
"""
df.shape[0]
print('Total rows in Dataframe is: ',  df.shape[0])
print('Total columns in Dataframe is: ',  df.shape[0])
"""
## Look at the datatypes in Dataframe
"""
df.dtypes
"""
<pre>------------------------------------------------------------------------------------------</pre>
"""
"""
# Slice and Dice i.e. Excel filters
"""
"""
Descriptive reporting is all about data subsets and aggregations, the moment we are to understand our data a little bit we start using filters to look at the smaller sets of data or view a particular column maybe to have a better understanding.
<br>Python offers a lot of different methods to slice and dice the dataframes, we'll play around with a couple of them to have an understanding of how it works
"""
"""
## View a specific column
"""
"""
There exists three main methods to select columns:

* Use dot notation: e.g. data.column_name
* Use square braces and the name of the column:, e.g. data['column_name']
* Use numeric indexing and the iloc selector data.loc[:, 'column_number']
"""
df['Name'].head()
df.iloc[:,[4]].head()
df.loc[:,['Name']].head()
"""
## View multiple columns
"""
df[['Name', 'PetalLength']].head()
#Pass a variable as a list
SpecificColumnList = ['Name', 'PetalLength']
df[SpecificColumnList].head()
"""
## View specific row's data
The method used here is slicing using the loc function, where we can specify the start and end row separated by colon
<br>Remember, __index starts from a 0 and not 1__
"""
df.loc[20:30] 
"""
## Slice rows and columns together
"""
df.loc[20:30, ['Name']]
"""
## Filter data in a column
"""
df[df['Name'] == 'Iris-versicolor'].head()
"""
## Filter multiple values
"""
df[df['Name'].isin(['Iris-versicolor', 'Iris-virginica'])]
"""
## Filter multiple values using a list
"""
Filter_Value = ['Iris-versicolor', 'Iris-virginica']
df[df['Name'].isin(Filter_Value)]
"""
## Filter values NOT in list or not equal to in Excel
"""
df[~df['Name'].isin(Filter_Value)]
"""
## Filter usinng using multiple conditions in multiple columns
__The input should always be a list__
<br>We can use this method to replicate advanced filter function in excel
"""
width = [2]
Flower_Name = ['Iris-setosa']
df[~df['Name'].isin(Flower_Name) & df['PetalWidth'].isin(width)]
"""
## Filter using numeric conditions
"""
df[df['SepalLength'] == 5.1].head()
df[df['SepalLength'] > 5.1].head()
"""
## Replicate the custom filter in Excel
"""
df[df['Name'].map(lambda x: x.endswith('sa'))]
"""
## Combine two filters to get the result
"""
df[df['Name'].map(lambda x: x.endswith('sa')) & (df['SepalLength'] > 5.1)]
"""
## Contains function in Excel
"""
df[df['Name'].str.contains('set')]
"""
## Get the unique values from dataframe
"""
df['SepalLength'].unique()
"""
If we want to view the entire dataframe with the unique values, we can use the drop_duplicates method
"""
df.drop_duplicates(subset=['Name'])
df.drop_duplicates(subset=['Name']).iloc[:,[3,4]]
"""
## Sort Values
"""
"""
Sort data by a certain column, by default the sorting is ascending
"""
df.sort_values(by = ['SepalLength'])
df.sort_values(by = ['SepalLength'], ascending = False)
"""
<pre>------------------------------------------------------------------------------------------</pre>
"""
"""
# Statistical summary of data
"""
"""
## __DataFrame Describe method:__ 
_Generate descriptive statistics that summarize the central tendency, dispersion and shape of a dataset’s distribution, excluding NaN values._
"""
df.describe()
"""
Summary stats of character columns
"""
df.describe(include = ['object'])
df.describe(include = 'all')
"""
<pre>------------------------------------------------------------------------------------------</pre>
"""
"""
# <centre>Data Aggregation</centre>
"""
"""
## Counting the unique values of a particular column. 
_Resulting output is a Series. You can refer it as a Single column Pivot Table_
"""
pd.value_counts(df['Name'])
"""
## Count cells
Count non-NA cells for each column or row.
"""
df.count(axis=0)
"""
## Sum
Summarising the data to get a snapshot of either by rows or columns
"""
df.sum(axis = 0) # 0 for column wise total
"""
Its replicates the method of adding a total column against each row
"""
df.sum(axis =1) # row wise sum
"""
## Add a total column to the existing dataset
"""
df['Total'] = df.sum(axis =1)
df.head()
"""
## Sum of specific columns, use the loc methos and pass the column names
"""
df['Total_loc']=df.loc[:,['SepalLength', 'SepalWidth']].sum(axis=1)
df.head()
"""
## Or, we can use the below method
"""
df['Total_DFSum']= df['SepalLength'] + df['SepalWidth']
df.head()
"""
### Don't like the new column, delete it using drop method
"""
df.drop(['Total_DFSum'], axis = 1)
"""
## Adding sum-total beneath each column
"""
Sum_Total = df[['SepalLength', 'SepalWidth', 'Total']].sum()
Sum_Total
T_Sum = pd.DataFrame(data=Sum_Total).T
T_Sum
T_Sum = T_Sum.reindex(columns=df.columns)
T_Sum
Row_Total = df.append(T_Sum,ignore_index=True)
Row_Total
"""
A lot has been done above, the approach that we are using is:
* Sum_Total: Do the sum of columns
* T_Sum: Convert the series output to dataframe and transpose
* Re-index to add missing columns
* Row_Total: append T_Sum to existing dataframe
"""
"""
## Sum based on criteria i.e. Sumif in Excel
"""
df[df['Name'] == 'Iris-versicolor'].sum()
"""
## Sumifs
"""
df[df['Name'].map(lambda x: x.endswith('sa')) & (df['SepalLength'] > 5.1)].sum()
"""
## Averageif
"""
df[df['Name'] == 'Iris-versicolor'].mean()
"""
## Averageifs
"""
df[df['Name'].map(lambda x: x.endswith('sa')) & (df['SepalLength'] > 5.1)].mean()
"""
## Max
"""
df[df['Name'] == 'Iris-versicolor'].max()
"""
## Min
"""
df[df['Name'] == 'Iris-versicolor'].min()
"""
# Groupby i.e. Subtotals in Excel
"""
df[['Name','SepalLength']].groupby('Name').sum()
GroupBy = df.groupby('Name').sum()
Group_By.append(pd.DataFrame(df[['SepalLength','SepalWidth','PetalLength','PetalWidth']].sum()).T)
"""
<pre>------------------------------------------------------------------------------------------</pre>
"""
"""
# Pivot Tables in Dataframes i.e. Pivot Tables in Excel
"""
"""
Who doesn'l love a Pivot Table in Excel, its one the best ways to analyse your data, have a quick overview of the information, helps you slice and dice the data with a super easy interface, helps you plots graphs basis on the data, add calculative columns etc.
<br>No, we wont have an interface to work, we'll have to explicitly write the code to get the output, No, it wont generate charts for you, but I don't think we can complete a tutorial without learning about the Pivot tables.
"""
pd.pivot_table(df, index= 'Name')#Same as Groupby
pd.pivot_table(df, values='SepalWidth', index= 'SepalLength',columns='Name', aggfunc = np.sum)
"""
A simple Pivot table showing us the sum of SepalWidth in values, SepalLength in Row Column and Name in Column Labels
"""
"""
Lets see if we can complicate it a bit.
"""
pd.pivot_table(df, values='SepalWidth', index= 'SepalLength',columns='Name', aggfunc = np.sum, fill_value=0)
"""
Blanks are now replaced with 0's by using the fill_value argument
"""
pd.pivot_table(df, values=['SepalWidth', 'PetalWidth'], index= 'SepalLength',columns='Name', aggfunc = np.sum, fill_value=0)
pd.pivot_table(df, values=['SepalWidth', 'PetalWidth'], index= ['SepalLength', 'PetalLength'],columns='Name', aggfunc = np.sum, fill_value=0)
pd.pivot_table(df, values=['SepalWidth', 'PetalWidth'], index= 'SepalLength',columns='Name', 
               aggfunc = {'SepalWidth': np.sum, 'PetalWidth': np.mean}, fill_value=0)
"""
We can have individual calculations on values using dictionary method and can also have multiple calculations on values
"""
pd.pivot_table(df, values=['SepalWidth', 'PetalWidth'], index= 'SepalLength',columns='Name', 
               aggfunc = {'SepalWidth': np.sum, 'PetalWidth': np.mean}, fill_value=0, margins=True)
"""
If we use margins argument, we can have total row added
"""
"""
<pre>------------------------------------------------------------------------------------------</pre>
"""
"""
# Vlookup
"""
"""
What a magical formula is vlookup in Excel, I think its the first thing that everyone wants to learn before learning how to even add. Looks fascinating when someone is applying vlookup, looks like magic when we get the output. Makes life easy. I can with very much confidence can say its the backbone of every data wrangling action performed on the spreadsheet.
<br>
<br>__Unfortunately__ we dont have a vlookup function in Pandas!
<br>
<br>Since we dont have a "Vlookup" function in Pandas, Merge is used as an alternate which is same as SQL. There are a total of four merge options available:
* ‘left’ — Use the shared column from the left dataframe and match to right dataframe. Fill in any N/A as NaN

* ‘right’ — Use the shared column from the right dataframe and match to left dataframe. Fill in any N/A as NaN

* ‘inner’ — Only show data where the two shared columns overlap. Default method.

* ‘outer’ — Return all records when there is a match in either left or right dataframe.
<br>
"""
df1 = pd.read_excel(filename)
lookup = df.merge(df,on='Name')
lookup
"""
The above might not be the best example to suppport the concept, however the working is the same.
"""
"""
<pre>------------------------------------------------------------------------------------------</pre>
"""
"""
I am hoping this tutorial made some sense, though I agree this could have been more elaborative which I will be pursuing soon.
<br>__Watch out the space for more of it.__
"""