import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import plotly.express as px
from plotly.subplots import make_subplots
import plotly.io as pio
pio.renderers.default = 'notebook_connected'Introduction to Data Science
Reading Data and Data Types
Important Information
- Email: joanna_bieri@redlands.edu
- Office Hours: Duke 209 Click Here for Joanna’s Schedule
Announcements
Exam 1 is handed out on Review Day, Thursday 10/1. Keep working on the Practice Exam from Day 7!
Please come to office hours to get help!
Day 8 Assignment
- Download Day8-HW.ipynb from the class website and move it into your
Day8folder in your repo. - Open the file Day8-HW.ipynb and start doing the problems.
- You can do these problems as you follow along with the lecture notes and video.
- Get as far as you can before class.
- Submit what you have so far Commit and Push to Git.
- Take the daily check in quiz on Canvas.
- Come to class with lots of questions!
Reading in Your Own Data
A big part of data science is finding your own data and then analyzing it. To be able to use Python to analyze data you first have to learn how to load data into a JupyterLab Notebook. There are lots of different ways, but in this class we will use Pandas (pd) to read data.
- pd.read_csv This command lets you read files that end in .csv. You can create a file like this using a spreadsheet program and saving the file as a .csv. Often you can download data in .csv format. You will need to make sure that your data is in the same folder as the code you are writing or that you can tell your code EXACTLY where the file is.
- pd.read_excel This command lets you read files created by Excel. Typically with .xlsx file types. You will need to make sure that your data is in the same folder as the code you are writing or that you can tell your code EXACTLY where the file is.
- pd.read_json This command lets you read .json files. JSON stands for JavaScript Object Notation. It is a very popular way to save data. You will need to make sure that your data is in the same folder as the code you are writing or that you can tell your code EXACTLY where the file is.
- pd.read_table If other methods fail, sometimes you can force Pandas to read a file by using the table method. You will need to make sure that your data is in the same folder as the code you are writing or that you can tell your code EXACTLY where the file is. You will have to tell what your delimiter is - what character separates the columns (sometimes this is a comma like in .csv, but sometimes it is something else like a colon or space)
Try reading in some data - csv
Go to the Cal Fire Website and scroll to the bottom to see the Incident Data. We will download the file named ALL DATA AS CSV this should put the data file into your Downloads folder.
Next you need to move the file mapdataall.csv from your Downloads folder into your Day8 folder where you are doing your homework. You can open your Downloads folder and drag the file into JupyterLab side bar. Then I can run the command
DF_raw = pd.read_csv('mapdataall.csv')
to load the data and look at the data frame.
NOTE: If you have downloaded this file before, your computer might name the new one mapdataall (1).csv. Rename it to mapdataall.csv before you run the code.
DF = pd.read_csv('mapdataall.csv')display(DF)| incident_name | incident_is_final | incident_date_last_update | incident_date_created | incident_administrative_unit | incident_administrative_unit_url | incident_county | incident_location | incident_acres_burned | incident_containment | ... | incident_latitude | incident_type | incident_id | incident_url | incident_date_extinguished | incident_dateonly_extinguished | incident_dateonly_created | is_active | calfire_incident | notification_desired | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Bridge Fire | Y | 2018-01-09T21:46:00Z | 2017-10-31T18:22:00Z | Shasta-Trinity National Forest | NaN | Shasta | I-5 and Turntable Bay, 7 miles NE of Shasta Lake | 37.0 | 100.0 | ... | 40.774000 | NaN | 2ca11d45-8139-4c16-8af0-880d99b21e82 | https://www.fire.ca.gov/incidents/2017/10/31/b... | 2018-01-09T21:46:00Z | 2018-01-09 | 2017-10-31 | N | False | False |
| 1 | Pala Fire | Y | 2020-09-16T21:07:35Z | 2009-05-24T21:56:00Z | CAL FIRE San Diego Unit | NaN | San Diego | Hwy 76 and Pala Temecula, northwest of Pala | 122.0 | 100.0 | ... | 1.000000 | Wildfire | 8f61f461-552d-4538-b186-35ab030da416 | https://www.fire.ca.gov/incidents/2009/5/24/pa... | 2009-05-25T07:00:00Z | 2009-05-25 | 2009-05-24 | N | True | False |
| 2 | River Fire | Y | 2022-10-24T18:39:23Z | 2013-02-24T16:16:00Z | CAL FIRE San Bernardino Unit | NaN | Inyo | south of Narrow Gauge Rd & north of Hwy 136, e... | 407.0 | 100.0 | ... | 36.602575 | NaN | 094719ba-a47b-4abb-9ec5-a506b2b9fd23 | https://www.fire.ca.gov/incidents/2013/2/24/ri... | 2013-03-01T04:00:00Z | 2013-03-01 | 2013-02-24 | N | True | False |
| 3 | Fawnskin Fire | Y | 2013-04-22T16:00:00Z | 2013-04-21T00:30:00Z | San Bernardino National Forest | NaN | San Bernardino | west of Delamar Mountain, north of the communi... | 30.0 | 100.0 | ... | 34.288877 | NaN | 58f89ff8-bd3e-4355-b1c0-8fa05c747d3f | https://www.fire.ca.gov/incidents/2013/4/20/fa... | 2013-04-22T16:00:00Z | 2013-04-22 | 2013-04-21 | N | False | False |
| 4 | Gold Fire | Y | 2013-05-01T14:00:00Z | 2013-04-30T19:59:00Z | CAL FIRE Madera-Mariposa-Merced Unit | NaN | Madera | Between Road 210 and Road 200 near Fine Gold C... | 274.0 | 100.0 | ... | 37.116295 | NaN | 357ffc13-bef9-48eb-810f-c5de851972eb | https://www.fire.ca.gov/incidents/2013/4/30/go... | 2013-05-01T14:00:00Z | 2013-05-01 | 2013-04-30 | N | True | False |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 3857 | Bunny Fire | Y | 2026-09-14T17:42:23Z | 2026-09-13T23:46:33Z | CAL FIRE Siskiyou Unit | NaN | Siskiyou | Bunny Lane and Lott Drive, Big Springs | 4.0 | 100.0 | ... | 41.634570 | Wildfire | 4de89474-b003-47e6-ae01-82a669161ecb | https://www.fire.ca.gov/incidents/2026/9/13/bu... | NaN | NaN | 2026-09-13 | N | True | False |
| 3858 | Bruns Fire | Y | 2026-09-15T13:34:03Z | 2026-09-12T23:28:23Z | CAL FIRE Santa Clara Unit | NaN | Alameda | Grant Line Road, Altamont | 15.0 | 100.0 | ... | 37.794882 | Wildfire | 368cd43b-9647-4729-a211-66e023b575c0 | https://www.fire.ca.gov/incidents/2026/9/12/br... | 2026-09-14T18:20:15Z | 2026-09-14 | 2026-09-12 | N | True | False |
| 3859 | Palos Fire | N | 2026-09-15T18:34:20Z | 2026-09-14T22:05:25Z | Los Angeles County Fire Department | NaN | Los Angeles | Lupin Hill Road and Las Palomas Drive, Whittier | 17.0 | 70.0 | ... | 33.968100 | Wildfire | 2806bcea-7e12-4de5-883a-3439244a0012 | https://www.fire.ca.gov/incidents/2026/9/14/pa... | NaN | NaN | 2026-09-14 | Y | False | False |
| 3860 | Dome Fire | N | 2026-09-16T20:52:13Z | 2026-09-15T15:28:00Z | National Park Service | NaN | Mariposa | North of Wawona Dome, Yosemite National Park | 1177.0 | NaN | ... | 37.570000 | Wildfire | 4883092e-485f-429d-878d-819f01172840 | https://www.fire.ca.gov/incidents/2026/9/15/do... | NaN | NaN | 2026-09-15 | Y | False | False |
| 3861 | Floriston Fire | N | 2026-09-16T22:18:15Z | 2026-09-16T17:01:00Z | Unified Command: CAL FIRE Nevada-Yuba-Placer U... | NaN | Nevada | I-80 and Floriston way, 9 miles northeast of T... | 300.0 | 5.0 | ... | 39.416068 | Wildfire | 653b14c1-b18e-4531-9290-9362f5a2fd1b | https://www.fire.ca.gov/incidents/2026/9/16/fl... | NaN | NaN | 2026-09-16 | Y | True | False |
3862 rows × 23 columns
Answer some questions about this data
Q How many variables and observations?
Q How many different incident types are there?
Q Make a histogram of the acres burned and color the bars by whether or not the incident was a calfire incident. You will probably need to make a mask to remove very small and very large fires. How many fires burned more than 100,000 acres? What is the largest fire in the data?
Q EXTRA - CHALLENGE - See if you can create a graph that answers the question: Are fires getting bigger or more frequent over time? You get complete creative control on how to answer this question!
Try reading in some data from a website
Here we will explore academy award winning films. Go to the Wiki for the List of Academy Award Winning Films. Look at what type of data is there. How many tables? Any weird looking data?
More and more websites block Python from reading their pages directly (you will see an error like HTTP Error 403: Forbidden). So I saved a copy of the main table from this page as a .csv file on the class website. Notice that you can give pd.read_csv() a web address instead of a file name!
file = 'https://joannabieri.com/introdatascience/data/academy_award_films.csv'
DF_raw = pd.read_csv(file)Let’s try a little analysis of this data.
display(DF_raw)| Film | Year | Awards | Nominations | |
|---|---|---|---|---|
| 0 | One Battle After Another | 2025 | 6 | 13 |
| 1 | Sinners | 2025 | 4 | 16 |
| 2 | Frankenstein | 2025 | 3 | 9 |
| 3 | KPop Demon Hunters | 2025 | 2 | 2 |
| 4 | Hamnet | 2025 | 1 | 8 |
| ... | ... | ... | ... | ... |
| 1396 | The Yankee Doodle Mouse | 1943 | 1 | 1 |
| 1397 | The Yearling | 1946 | 2 | 7 |
| 1398 | Yesterday, Today and Tomorrow (Ieri, oggi, dom... | 1964 | 1 | 1 |
| 1399 | You Can't Take It with You | 1938 | 2 | 7 |
| 1400 | Zorba the Greek (Alexis Zorbas) | 1964 | 3 | 7 |
1401 rows × 4 columns
DF_raw.describe()| Film | Year | Awards | Nominations | |
|---|---|---|---|---|
| count | 1401 | 1401 | 1401 | 1401 |
| unique | 1387 | 98 | 22 | 25 |
| top | A Star Is Born | 1945 | 1 | 1 |
| freq | 3 | 21 | 998 | 560 |
Notice that we are getting very limited statistical information!! What is going wrong here?
Something is wrong with our data!
What should the data types be:
- Film - should be categorical (a word or string)
- Year - should be discrete numerical (a number or integer)
- Awards - should be discrete numerical (a number or integer)
- Nominations - should be discrete numerical (a number or integer)
So we should be able to get good statistics on ‘Year’, ‘Awards’, and ‘Nominations’. Let’s sub select just these columns.
my_columns = ['Year', 'Awards', 'Nominations']
DF_raw[my_columns].describe()| Year | Awards | Nominations | |
|---|---|---|---|
| count | 1401 | 1401 | 1401 |
| unique | 98 | 22 | 25 |
| top | 1945 | 1 | 1 |
| freq | 21 | 998 | 560 |
This still is not making sense. What if I wanted to know the average number of awards?
Something is really wrong with our data - I get an error!
DF_raw['Awards'].mean()TypeError: Could not convert string '64321111111111532221111111117421111111111742111111111136211111111111132212221121111142111111223111113433111111111114322221111111110 (1)[1]36221111111111126311111111111114431111111111111373221111111343222111111111552111111111111443222111111163222111111111832211111111143221111111111111432221111111111133331111111111114522111111111111122111111111632211111111111442211111111111154411111111115422111111111175311111111111222111111192111111111111152222111111111110 (1)6222111111111111733211111111433211111111111542221111111173211111111110 (1)432221111111111433 (1)11111111111119311111111110 (1)43321111111111117221111111111118322111111110 (1)544211111110 (1)842111111111144 (1)3321111114322211 (1)1111154221111111110 (1)532111111111110 (1)46 (1)31 (1)111111113442111111110 (1)0 (1)5431111111110 (2)632111 (1)11111117221111111111381 (1)1111111115322211111111721111111111111343211111111115 (1)3211111111111110 (1)532221111111116532221111115532111111110 (1)321311111162131161112145115115211211111117 (1)10 (1)1120 (1)1112111111111121211711111111113113121111130 (1)111411111211111111111111112111113111111211111131111110 (2)211211111110 (1)0 (1)1180 (1)21 (1)13119111171118 (2)2111112121113211411114110 (1)2114111110 (1)1531311211111120 (1)4111510 (1)22 (1)111122111512111110 (1)1111117111231110 (2)11111224311111114511211113210 (1)11111211161111811221111111111111211181211111112221112610 (1)1111111111112110 (1)1121221323120 (1)141111 (1)1112121111110 (1)1111110 (1)1111241 (1)111140 (1)111121111 (1)13114111331110 (1)1110 (1)1111131111112111111111111134111111131111211111111111110 (1)1111101111111111521111211111111312123' to numeric
Help my data is broken!
What you see happening here is that the data is not in the format that you expected. Now as a data scientist you have to decide what to do!
Data Types
In python there are four data types that we will work with most:
- String - this is a word, sentence, letter… surrounded by quotes.
- Integer - this is a number with no decimal points - fractions not allowed (rounded)
- Float - this is a number with decimal places - fractions allowed
- Boolean - this is True or False
Let’s decipher the error message:
“Could not convert string — to numeric.”
This is telling us that when Python tried to take that average it ran into strings (words). It then tried to convert those words into numbers, but ran into problems. So it cannot take the average here.
Data Type Exploration
Below are some variables that contain different data - we will use the type() command to see what data type they are.
num1 = 1
num2 = 1.0
num3 = '1'
num4 = 1.6
num5 = '1.2'print(type(num1))
print(type(num2))
print(type(num3))
print(type(num4))
print(type(num5))<class 'int'>
<class 'float'>
<class 'str'>
<class 'float'>
<class 'str'>
Does this result match our definitions above?
Changing Data Types
Now what if we wanted to change these data types from one type to another? We can use the commands:
- str() - to change to a string
- int() - to change to an integer
- float() - to change to a float
Intentional Converting Between Types:
num1_str = str(num1)
print(type(num1_str))
num1_str<class 'str'>
'1'
num2_int = int(num2)
print(type(num2_int))
num2_int<class 'int'>
1
num3_int = int(num3)
print(type(num3_int))
num3_int<class 'int'>
1
# int() will always round down!
num4_int = int(num4)
print(type(num4_int))
num4_int<class 'int'>
1
num5_float = float(num5)
print(type(num5_float))
num5_float<class 'float'>
1.2
Unintentional Converting Between Types
Sometimes Python functions will automatically convert between types or make type decisions for you!
Here is an example of adding an integer and a float. Python returns the result as a float.
num1 = 1
num2 = 1.0
type(num1+num2)
num1+num22.0
Here we see that even though we send in integers, when we take the average it returns a float.
my_numbers = [2,2,2,2]
np.mean(my_numbers)np.float64(2.0)
Fun with Strings!
Many times your data issues will arise when someone enters data in a wrong format. This often means that you have to deal with strings that need to be fixed. Here are some useful commands
- .lstrip() - removes left white space
- .rstrip() - removes right white space
- .replace(string1, string2) - replaces string1 with string2 as long as string1 is part of the original string
- .split(character) - splits the string into chunks based on the character provided
There are lots more, but this is enough to learn for now!
my_string = ' Year: 2024/25 '
len(my_string)19
my_string = my_string.lstrip()
len(my_string)16
my_string = my_string.rstrip()
len(my_string)13
my_string = my_string.replace(':',',')
my_string'Year, 2024/25'
my_broken_string = my_string.split('/')
my_broken_string['Year, 2024', '25']
# Now we can select just one part of the original string
my_broken_string[1]'25'
So How Can I Fix my Data?
Remember that this entire problem started when we were exploring the movie data stored in DF_raw. Let’s learn a new command to help us see what type of data is in the DataFrame. To report the type of data in each variable use the command
DF_raw.dtypes
DF_raw.dtypesFilm object
Year object
Awards object
Nominations object
dtype: object
object means that the data is a string or that there is at least one string in the column. Does it make sense for year to be a string?
year_data = DF_raw['Year'].value_counts()
DF_year = year_data.reset_index().rename(columns={"index": "value", 0: "count"})
DF_year| Year | count | |
|---|---|---|
| 0 | 1945 | 21 |
| 1 | 1949 | 20 |
| 2 | 1942 | 20 |
| 3 | 1950 | 19 |
| 4 | 1948 | 19 |
| ... | ... | ... |
| 93 | 1931/32 | 10 |
| 94 | 1932/33 | 9 |
| 95 | 1928/29 | 7 |
| 96 | 1929/30 | 6 |
| 97 | 1930/31 | 6 |
98 rows × 2 columns
There are some years that look like normal integer years and others that are formatted like 1931/32. How can we fix this so we can do our analysis?
This is more of an art than a science - as a data scientist you need to make these types of decisions and keep track of why and how you make the decision. For the data above, my plan is to remove the extra data /32 and just use the first part of the string for year.
.apply() and lambda
The .apply() command lets you apply a function to every row along your data frame.
lambda is a special function you first write the word lambda and choose a variable followed by a colon
lambda x:then you give an operation to do to every x (in this case every entry in the “Year” column)
x.split('/')[0]this will split up the string so that 1930/31 becomes [1930, 31] then the [0] just selects the first part of the list.
We can apply this operation and save the information over the “Year” column so the data will be in a better format!
DF_raw['Year'] = DF_raw['Year'].apply(lambda x: x.split('/')[0])year_data = DF_raw['Year'].value_counts()
DF_year = year_data.reset_index().rename(columns={"index": "value", 0: "count"})
DF_year| Year | count | |
|---|---|---|
| 0 | 1945 | 21 |
| 1 | 1949 | 20 |
| 2 | 1942 | 20 |
| 3 | 1950 | 19 |
| 4 | 1948 | 19 |
| ... | ... | ... |
| 93 | 1931 | 10 |
| 94 | 1932 | 9 |
| 95 | 1928 | 7 |
| 96 | 1929 | 6 |
| 97 | 1930 | 6 |
98 rows × 2 columns
Now we will turn all the data in the year column into integers. There are lots of ways to do this, but lets practice the .apply() with a lambda method.
DF_raw['Year'] = DF_raw['Year'].apply(lambda x: int(x))DF_raw.dtypesFilm object
Year int64
Awards object
Nominations object
dtype: object
Notice that we fixed the year column! Lets start working on the other columns.
Do the same type of thing to the Awards column
Look at what kind of data is in the “Awards” column.
award_data = DF_raw['Awards'].value_counts()
DF_award = award_data.reset_index().rename(columns={"index": "value", 0: "count"})
DF_award| Awards | count | |
|---|---|---|
| 0 | 1 | 998 |
| 1 | 2 | 160 |
| 2 | 3 | 79 |
| 3 | 4 | 48 |
| 4 | 0 (1) | 32 |
| 5 | 5 | 26 |
| 6 | 6 | 13 |
| 7 | 7 | 12 |
| 8 | 1 (1) | 8 |
| 9 | 8 | 7 |
| 10 | 9 | 3 |
| 11 | 11 | 3 |
| 12 | 0 (2) | 3 |
| 13 | 0 (1)[1] | 1 |
| 14 | 4 (1) | 1 |
| 15 | 3 (1) | 1 |
| 16 | 6 (1) | 1 |
| 17 | 5 (1) | 1 |
| 18 | 7 (1) | 1 |
| 19 | 8 (2) | 1 |
| 20 | 2 (1) | 1 |
| 21 | 10 | 1 |
What the heck are these extra () and [] numbers?
To understand this better I had to go back to the website. It seems that things in parenthesis are special awards and things in square brackets are citations.
So I will choose to remove this information. THIS IS JUST ONE CHOICE - alternatively we could choose to add another column just for special awards if these were important to our analysis.
Looking at the data, I only want the numbers to the left of a ( so I will use this character to split my strings. I will also turn all the data into integer types.
DF_raw['Awards'] = DF_raw['Awards'].apply(lambda x: int(x.split('(')[0]))award_data = DF_raw['Awards'].value_counts()
DF_award = award_data.reset_index().rename(columns={"index": "value", 0: "count"})
DF_award| Awards | count | |
|---|---|---|
| 0 | 1 | 1006 |
| 1 | 2 | 161 |
| 2 | 3 | 80 |
| 3 | 4 | 49 |
| 4 | 0 | 36 |
| 5 | 5 | 27 |
| 6 | 6 | 14 |
| 7 | 7 | 13 |
| 8 | 8 | 8 |
| 9 | 11 | 3 |
| 10 | 9 | 3 |
| 11 | 10 | 1 |
DF_raw.dtypesFilm object
Year int64
Awards int64
Nominations object
dtype: object
You Try - Nominations column
Q Now it is your turn. Fix the Nominations column so that all the data is in integer format.
First look at the data and notice that the [ character seems to split the number of nominations from something that looks like a reference.
nom_data = DF_raw['Nominations'].value_counts()
DF_nom = nom_data.reset_index().rename(columns={"index": "value", 0: "count"})
display(DF_nom)
print('I can see that I want the data to the left of the [ character')
DF_raw['Nominations'] = DF_raw['Nominations'].apply(lambda x: int(x.split('[')[0]))
nom_data = DF_raw['Nominations'].value_counts()
DF_nom = nom_data.reset_index().rename(columns={"index": "value", 0: "count"})
display(DF_nom)
DF_raw.dtypes
If everything went to plan you should see:
DF_raw.dtypesFilm object
Year int64
Awards int64
Nominations object
dtype: object
Now we can do some statistics and continue our analysis!
DF_raw.describe()| Year | Awards | |
|---|---|---|
| count | 1401.000000 | 1401.000000 |
| mean | 1976.836545 | 1.576017 |
| std | 27.982361 | 1.390826 |
| min | 1927.000000 | 0.000000 |
| 25% | 1952.000000 | 1.000000 |
| 50% | 1976.000000 | 1.000000 |
| 75% | 2002.000000 | 2.000000 |
| max | 2025.000000 | 11.000000 |
Warning - Be Kind!
Fixing messy data can be a challenging and complicated process. Please be nice to yourself!
What is expected in this class:
- You can find some data online and load it into Python
- You can figure out what types of data are in each column
- You can say in words what format you would like the data to be in.
- You can patiently TRY to fix the data.
- You come get help from your Professor, TA, or friends before you get too frustrated!
Try to find some data of your own.
Your homework today will be to see if you can find some data of your own. This can be the first steps you take toward your final project.