<jupyter_start><jupyter_text>Introduction to pandas_Adapted from original materials by [<NAME>](http://gregreda.com/)._ What is it?[pandas](http://pandas.pydata.org/) is an open source [Python](http://www.python.org/) library for data analysis. Python has always been great for prepping and munging data, but historically it has not been great for analysis - you'd usually end up using [R](http://www.r-project.org/) or loading it into a database and using SQL (or worse, Excel). pandas makes Python great for analysis.<jupyter_code>import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from IPython.display import display
# Switch to truncated view if too many columns
pd.set_option('max_columns', 50)
%matplotlib inline<jupyter_output><empty_output><jupyter_text>Data Structurespandas introduces two new data structures to Python - [Series](http://pandas.pydata.org/pandas-docs/dev/dsintro.htmlseries) and [DataFrame](http://pandas.pydata.org/pandas-docs/dev/dsintro.htmldataframe), both of which are built on top of [NumPy](http://www.numpy.org/) (this means it's fast).  Series A Series is a one-dimensional object similar to an array, list, or column in a table. It will assign a labeled index to each item in the Series. By default, each item will receive an index label from 0 to N, where N is the length of the Series minus one.<jupyter_code># create a Series with an arbitrary list
s = pd.Series([7, 'Heisenberg', 3.14, -1789710578, 'Happy Eating!'])
s<jupyter_output><empty_output><jupyter_text>Alternatively, you can specify an index to use when creating the Series.<jupyter_code>s = pd.Series([7, 'Heisenberg', 3.14, -1789710578, 'Happy Eating!'],
              index=['A', 'Z', 'C', 'Y', 'E'])
s<jupyter_output><empty_output><jupyter_text>The Series constructor can convert a dictonary as well, using the keys of the dictionary as its index.<jupyter_code>d = {'Chicago': 1000, 'New York': 1300, 'Portland': 900, 'San Francisco': 1100,
     'Austin': 450, 'Boston': None}
cities = pd.Series(d)
cities<jupyter_output><empty_output><jupyter_text>You can use the index to select specific items from the Series ...<jupyter_code>cities['Chicago']
cities[['Chicago', 'Portland', 'San Francisco']]<jupyter_output><empty_output><jupyter_text>Or you can use boolean indexing for selection.<jupyter_code>cities[cities < 1000]<jupyter_output><empty_output><jupyter_text>That last one might be a little weird, so let's make it more clear - `cities < 1000` returns a Series of True/False values, which we then pass to our Series `cities`, returning the corresponding True items.<jupyter_code>less_than_1000 = cities < 1000
print(less_than_1000)
print('\n')
print(cities[less_than_1000])<jupyter_output><empty_output><jupyter_text>You can also change the values in a Series on the fly.<jupyter_code># changing based on the index
print('Old value:', cities['Chicago'])
cities['Chicago'] = 1400
print('New value:', cities['Chicago'])
cities
# changing values using boolean logic
print(cities[cities < 1000])
print('\n')
cities[cities < 1000] = 750

print(cities[cities < 1000])<jupyter_output><empty_output><jupyter_text>What if you aren't sure whether an item is in the Series?  You can check using idiomatic Python.<jupyter_code>print('Seattle' in cities)
print('San Francisco' in cities)<jupyter_output><empty_output><jupyter_text>Mathematical operations can be done using scalars and functions.<jupyter_code># divide city values by 3
cities/3
# square city values
np.square(cities)<jupyter_output><empty_output><jupyter_text>You can add two Series together, which returns a union of the two Series with the addition occurring on the shared index values.  Values on either Series that did not have a shared index will produce a NULL/NaN (not a number).<jupyter_code>print(cities[['Chicago', 'New York', 'Portland']])
print('\n')
print(cities[['Austin', 'New York']])
print('\n')
print(cities[['Chicago', 'New York', 'Portland']] + cities[['Austin', 'New York']])<jupyter_output><empty_output><jupyter_text>Notice that because Austin, Chicago, and Portland were not found in both Series, they were returned with NULL/NaN values.NULL checking can be performed with `isnull` and `notnull`.<jupyter_code># returns a boolean series indicating which values aren't NULL
print(cities)
cities.notnull()
# use boolean logic to grab the NULL cities
print(cities.isnull())
print('\n')
print(cities[cities.isnull()])<jupyter_output><empty_output><jupyter_text>DataFrameA DataFrame is a tablular data structure comprised of rows and columns, akin to a spreadsheet, database table, or R's data.frame object. You can also think of a DataFrame as a group of Series objects that share an index (the column names).For the rest of the tutorial, we'll be primarily working with DataFrames.  Reading DataTo create a DataFrame out of common Python data structures, we can pass a dictionary of lists to the DataFrame constructor.Using the `columns` parameter allows us to tell the constructor how we'd like the columns ordered. By default, the DataFrame constructor will order the columns alphabetically (though this isn't the case when reading from a file - more on that next).<jupyter_code>data = {'year': [2010, 2011, 2012, 2011, 2012, 2010, 2011, 2012],
        'team': ['Bears', 'Bears', 'Bears', 'Packers', 'Packers', 'Lions', 'Lions', 'Lions'],
        'wins': [11, 8, 10, 15, 11, 6, 10, 4],
        'losses': [5, 8, 6, 1, 5, 10, 6, 12]}
football = pd.DataFrame(data, columns=['year', 'team', 'wins', 'losses'])
football<jupyter_output><empty_output><jupyter_text>Much more often, you'll have a dataset you want to read into a DataFrame. Let's go through several common ways of doing so. **CSV**Reading a CSV is as simple as calling the *read_csv* function. By default, the *read_csv* function expects the column separator to be a comma, but you can change that using the `sep` parameter.<jupyter_code>%cd ./data
!ls -lrt
# Source: baseball-reference.com/players/r/riverma01.shtml
!head -n 5 mariano-rivera.csv
from_csv = pd.read_csv('mariano-rivera.csv')
from_csv.head()<jupyter_output><empty_output><jupyter_text>Our file had headers, which the function inferred upon reading in the file. Had we wanted to be more explicit, we could have passed `header=None` to the function along with a list of column names to use:<jupyter_code># Source: pro-football-reference.com/players/M/MannPe00/touchdowns/passing/2012/
!head -n 5 peyton-passing-TDs-2012.csv
cols = ['num', 'game', 'date', 'team', 'home_away', 'opponent',
        'result', 'quarter', 'distance', 'receiver', 'score_before',
        'score_after']
no_headers = pd.read_csv('peyton-passing-TDs-2012.csv', sep=',', header=None,
                         names=cols)
no_headers.head()<jupyter_output><empty_output><jupyter_text>pandas various *reader* functions have many parameters allowing you to do things like skipping lines of the file, parsing dates, or specifying how to handle NA/NULL datapoints.There's also a set of *writer* functions for writing to a variety of formats (CSVs, HTML tables, JSON).  They function exactly as you'd expect and are typically called `to_format`:```pythonmy_dataframe.to_csv('path_to_file.csv')```[Take a look at the IO documentation](http://pandas.pydata.org/pandas-docs/stable/io.html) to familiarize yourself with file reading/writing functionality.<jupyter_code># You can also get help by appending ? to any command
#pd.read_csv?<jupyter_output><empty_output><jupyter_text>**_Exercise_*** Search the web for a sample CSV file (with/without headers, colon-separated, etc), save it, and load it into a pandas DataFrame **PostgreSQL Database** pandas also has some support for reading/writing DataFrames directly from/to a database [[docs](http://pandas.pydata.org/pandas-docs/stable/io.htmlsql-queries)].  You'll typically just need to pass a connection object to the `read_sql` or `to_sql` functions within the `pandas.io` module.Note that `to_sql` executes as a series of INSERT INTO statements and thus trades speed for simplicity. If you're writing a large DataFrame to a database, it might be quicker to write the DataFrame to CSV and load that directly using the database's file import arguments.<jupyter_code>from sqlalchemy import create_engine<jupyter_output><empty_output><jupyter_text>Substitute the variables below with the access credentials provided by David last Friday.<jupyter_code>engine = create_engine('postgresql://dsr:DSRinBerlin!@ds<EMAIL>:5432/rentals')
films = pd.read_sql_query('select * from film limit 200', con=engine)
films.head()<jupyter_output><empty_output><jupyter_text>**_Exercise_*** Try reproducing some of the groupby and join exercises that you did with David last Friday, translated into pandas. **Clipboard**While the results of a query can be read directly into a DataFrame, I prefer to read the results directly from the clipboard. I'm often tweaking queries in my SQL client ([Sequel Pro](http://www.sequelpro.com/)), so I would rather see the results *before* I read it into pandas. Once I'm confident I have the data I want, then I'll read it into a DataFrame.This works just as well with any type of delimited data you've copied to your clipboard. The function does a good job of inferring the delimiter, but you can also use the `sep` parameter to be explicit.[<NAME>](http://www.baseball-reference.com/players/a/aaronha01.shtml)![hank-aaron-stats-screenshot](http://i.imgur.com/xiySJ2e.png)<jupyter_code>hank = pd.read_clipboard()
hank.head()<jupyter_output><empty_output><jupyter_text>**URL**With `read_table`, we can also read directly from a URL.Let's use the [best sandwiches data](https://raw.github.com/gjreda/best-sandwiches/master/data/best-sandwiches-geocode.tsv) that I [wrote about scraping](http://www.gregreda.com/2013/05/06/more-web-scraping-with-python/) a while back.<jupyter_code>url = 'https://raw.github.com/gjreda/best-sandwiches/master/data/best-sandwiches-geocode.tsv'

# fetch the text from the URL and read it into a DataFrame
from_url = pd.read_table(url, sep='\t')
from_url.head()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10992063492063492, 'fraction_numerical': 0.018353174603174604, 'mean_word_length': 5.654125412541254, 'pattern_counts': {'":': 0, '<': 90, '<?xml version=': 0, '>': 84, 'https://': 2, 'lorem ipsum': 0, 'www.': 6, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '20', 'dir': 'jupyter-structured-clean-dedup', 'id': '568959', 'license': 'MIT', 'n_tokens_mistral': 3333, 'n_tokens_neox': 3069, 'n_words': 1127, 'path': 'intro-to-pandas-1-Data-Structures.ipynb', 'repo_name': 'L2Data/pandas-tutorial', 'size': '10130'}