# libraries used throughout this notebook
import pandas as pd
import numpy as np

"""
Explanation: <div align="right">Python [conda env:PY27_rtclone]</div>
<div align="right">Python [conda env:PY36_clone]</div>

Working with Pandas DataFrames
This notebook was created in Python 2.7 and cross-tested in Python 3.6.  Code should work in both versions.  It explores basic syntax of working with Pandas Dataframes.  Some useful functions and one line snippets to know:
- DataFrame(data={key1:[data, data2], key2:[d, d2]}, columns=["col1", "col2"], index=indexVar, dtype=int64)
- data_df.astype(dtype= {"wheel_number":"int64", "car_name":"object","minutes_spent":"float64"})
- as.dataframe(df_data)
TOC

Building a Simple DataFrame
from a Dictionary
Create Empty and Add Rows One by One
Setting dtypes on Each Column of DF
Renaming Columns
Displaying Dataframe with nice Notebook Formatting<br/><br/>
Indexing and Selecting Dataframe Slices<br/><br/>
More Help on DFs on The Web<br/><br/>
End of explanation
"""


# create a dictionary
stateData = {'state': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada'], \
        'year': [2000, 2001, 2002, 2001, 2002],
        'pop': [1.5, 1.7, 3.6, 2.4, 2.9]}
# convert to DataFrame
df_stdt = pd.DataFrame(stateData)
df_stdt

print("Index:  %s" %df_stdt.index)
print("Values:  \n%s" %df_stdt.values)
print("Columns: %s" %df_stdt.columns)
print("dataframe.describe():")
df_stdt.describe()

"""
Explanation: <a id="bld1" name="bld1"></a>
Building a Simple DataFrame
<a id="dixndf" name="dixndf"></a>
Create DF from Dictionary
End of explanation
"""


solutionPD = pd.DataFrame({ 'disk':[],'fromPeg':[], 'toPeg':[]}, dtype=np.int64 ) 
solutionPD = solutionPD.append(pd.DataFrame({ 'disk':[5],'fromPeg':[1], 'toPeg':[3]}), ignore_index=True)
solutionPD = solutionPD.append(pd.DataFrame({ 'disk':[4],'fromPeg':[1], 'toPeg':[2] }), ignore_index=True)
print("disk column type: %s" %type(solutionPD['disk'][0]))
solutionPD

"""
Explanation: <a id="lbldf" name="lbldf"></a>
Create Empty DF And Add Rows One At A Time
A common scenario in code:  a loop or function call needs to add a row to a dataframe but first you need a blank one.  This code may come in handy as that scenario comes up.
End of explanation
"""


# Recommended:  To set dtype for all columns, build the Dataframe, 
# and then use astype() to set the columns as shown here

# though online help topics indicate it should be possible to pass in a dictionary or list of tuples
# to set all the datatypes initially, under Python 2.7, this does not seem to work.
# came closest with this sample:
#   solutionPD2 = pd.DataFrame({ 'disk':[],'fromPeg':[], 'toPeg':[], 'text':[], 'notes':[]}, 
#                            dtype=[('disk', 'int64'), ('fromPeg', 'int64' ), ('toPeg', 'float64'), 
#                                   ('text', 'str'), ('notes', 'object')] ) 
# Related links:
#    http://stackoverflow.com/questions/21197774/assign-pandas-dataframe-column-dtypes
#    http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.append.html

solutionPD2 = pd.DataFrame({ 'disk':[],'fromPeg':[], 'toPeg':[], 'notes':[], 'text':[]}, dtype=np.int64) 
solutionPD2 = solutionPD2.astype({ 'disk':'int64','fromPeg':'float64', 'toPeg':'float64', 'notes':'object', 
                                   'text':'str'})

solutionPD2 = solutionPD2.append(pd.DataFrame({ 'disk':[5],'fromPeg':[1], 'toPeg':[3], 
                                                'notes':'hello', 'text':'more text' }), ignore_index=True)
solutionPD2 = solutionPD2.append(pd.DataFrame({ 'disk':[4],'fromPeg':[1], 'toPeg':[2], 
                                                'notes':'good bye', 'text':'even more text'}), ignore_index=True)

# print("disk column type: %s" %type(solutionPD['disk'][0]))
print(solutionPD2.dtypes)
solutionPD2

# disk is currently an int
# we will convert this numeric column to text here
solutionPD2.disk = solutionPD2.disk.astype('str')
print(solutionPD2.dtypes)  # disk changes to 'object' which permits text

"""
Explanation: <a id="setDtype1" name="setDtype1"></a>
Setting Data Types on Columns
Read comments as well as code in cells which follow:
End of explanation
"""


testDF = pd.DataFrame({"color":["red", "green", "blue"],
              "thing":["house", "dog", "machine"]})

testDF

## get single column name
testDF.columns[0]  

## rename one column
testDF.columns.values[0]='primaries'
testDF

testDF.primaries

testDF.color

testDF.color.name

testDF.columns = ['primary', 'something']
testDF

testDF.something

testDF.primary

## stackoverflow: https://stackoverflow.com/questions/11346283/renaming-columns-in-pandas/11346337
##   sample: df.rename(columns={'oldName1': 'newName1', 'oldName2': 'newName2'}, inplace=True)

testDF.rename(columns={'primary':'myColors'}, inplace=True)
testDF

testDF.rename(columns={'myColors':'TruColors', 'something':'anything'}, inplace=True)
testDF

"""
Explanation: <a id="rname" name="rname"></a>
Renaming Dataframe Columns
Study these tests.  They show what works and what doesn't as counterintuitive as that is.  This code was written python 3.6.1
End of explanation
"""


## this does not work:

def printDF(df):
    print(df)

def displayDF(df):
    df

df_stdt            # fails to output since this is not the last command in the cell
print(df_stdt)     # outputs but without the nice built-in formatting
print("="*72)
printDF(df_stdt)   # outputs without format
displayDF(df_stdt) # fails to output since it is not the last command in the dell

"""
Explanation: <a id="display1" name="display1"></a>
Displaying DataFrames in Python Notebooks
To the uninitiated, dataframes appear to display nicely in notebooks if they are the last command in a cell.  But printing them and/or outputting them from functions does not work unless you "know the trick" illustrated in this section.
The Problem
End of explanation
"""


## use this line if it works ... if not, try the one used at start of this code cell
## this cell tested with Anaconda / Python 3.6
# from Ipython.display import display

import ipython_genutils

def presentDF(df):
    display(df)
    
presentDF(df_stdt)  
print("="*72)
display(solutionPD)
print("="*72)
display(solutionPD2)
print("="*72)
df_stdt

"""
Explanation: The Solution
A Stack Overflow post  indicates to use the import statement that is commented out in the cell that follows.
The one that is not commented out works with the Python 3.6 Anaconda implementation tested here.
By default: this Anaconda implementation installed ipython and ipython_genutils packages.  If these are missing, they would need to be installed using conda install &lt;package&gt;.  You can use the --dry-run switch to test that the install is compatible with your specific package environment first.
End of explanation
"""


testDF

testDF.iloc[:,0] # all rows, column 0

testDF.iloc[0,0] # row 0, column 0

testDF.iloc[1,:]  # row 1, all columns

testDF.loc[1]  # row at index 1

## note: this DF had the names changed but indicies for columns are different
testDF.loc[:, 'TruColors']  # all rows, column by index name

testDF.loc[:, 'TruColors':'anything']  # if there were more columns - this would get all column in this range

testDF.loc[1:2, 'anything']  # using index names ... with names it is 1:2 including 2

testDF.iloc[1:3, 1:]  # using index numbers ... note that with numbers it is up to 3 (so 1 and 2)

"""
Explanation: <a id="indx" name="indx"></a>
Indexing for DataFrame Slices
There are some good resources on the web about this.  This list will get added to as they are discovered.

Tutorial: selecting dataframes
Pandas Dataframe by Exaple - see the sections on selecting and indexing

Quick Example: .loc[], .iloc[]
End of explanation
"""


