Thursday, November 7, 2019

Individual household electric power consumption Data: Download and cleaning

1. Data download and cleaning

Part 1. Data Set download, cleaning and preparation

The set is taken from here:

https://archive.ics.uci.edu/ml/datasets/individual+household+electric+power+consumption#

The file has extention .txt and it contains a semicolon delimited table with a header.

I start with downloading Python modules.

In [1]:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from datetime import date
from datetime import timedelta

Data download and initial check

Let us download the data, look at first rows and check dimensions.

In [2]:
power_consumption = pd.read_csv('household_power_consumption.txt', sep=';', low_memory=False)
power_consumption.head(), power_consumption.shape
Out[2]:
(         Date      Time Global_active_power Global_reactive_power  Voltage  \
 0  16/12/2006  17:24:00               4.216                 0.418  234.840   
 1  16/12/2006  17:25:00               5.360                 0.436  233.630   
 2  16/12/2006  17:26:00               5.374                 0.498  233.290   
 3  16/12/2006  17:27:00               5.388                 0.502  233.740   
 4  16/12/2006  17:28:00               3.666                 0.528  235.680   
 
   Global_intensity Sub_metering_1 Sub_metering_2  Sub_metering_3  
 0           18.400          0.000          1.000            17.0  
 1           23.000          0.000          1.000            16.0  
 2           23.000          0.000          2.000            17.0  
 3           23.000          0.000          1.000            17.0  
 4           15.800          0.000          1.000            17.0  ,
 (2075259, 9))

As we see the table has 2 million rows, and the date column has European standard: a day number before a month number.

What are our columns types?

In [3]:
power_consumption.dtypes
Out[3]:
Date                      object
Time                      object
Global_active_power       object
Global_reactive_power     object
Voltage                   object
Global_intensity          object
Sub_metering_1            object
Sub_metering_2            object
Sub_metering_3           float64
dtype: object

What is a range for the numbers of records for a day?

In [4]:
number_of_entries_per_day = dict()
for day in power_consumption.Date:
    if day in number_of_entries_per_day:
        number_of_entries_per_day[day] +=1
    else:
        number_of_entries_per_day[day] = 1

frequences = number_of_entries_per_day.values()
min(frequences), max(frequences)
Out[4]:
(396, 1440)
In [5]:
plt.hist(frequences, bins=10)
plt.show()

As we see a vast majority of days have about the same amount of records, which is good for analysis.

How many observations we have in an hour during 24-hour period?

In [6]:
number_of_entries_per_day_and_hour = dict()
for day, entry in zip(power_consumption.Date, power_consumption.Time):
    hour = entry[:2]
    if (day,hour) in number_of_entries_per_day_and_hour:
        number_of_entries_per_day_and_hour[(day,hour)] +=1
    else:
        number_of_entries_per_day_and_hour[(day,hour)] = 1

frequences = number_of_entries_per_day_and_hour.values()
min(frequences), max(frequences)
Out[6]:
(3, 60)
In [7]:
plt.hist(frequences, bins=10)
plt.show()

The next question is: do we have any missing days?

We can figure out that years for our data are 2006, 2007, 2008, 2009, 2010, because the first year is 2006 and we have 47 month of data. Recall that a year in our Date column takes last 4 places.

In [8]:
days_in_a_year = {2006: (0), 2007:(0), 2008: (0), 2009:(0), 2010:(0)}

for day in number_of_entries_per_day:
    year = int(day[-4:])
    days_in_a_year[year] += 1

days_in_a_year
Out[8]:
{2006: 16, 2007: 365, 2008: 366, 2009: 365, 2010: 330}

Clearly we are fine with years 2007, 2008, 2009. We are good even with 2006 if we think about it: the only days for it are in December, and here are 31 days in the month. We start our records from 16th, so 15 days in December were without records, and 31-15 = 16. The year 2010 is more complicated. But we can get enumeration of year days in our data for the year, and check if the list contains all intermediate numbers.

In [9]:
year2010days = [pd.to_datetime(date).strftime('%j') for date  in number_of_entries_per_day.keys() if date[-4:] == '2010']
min(year2010days), max(year2010days)
Out[9]:
('001', '345')

We obviously have some missing days. Let us see which ones.

In [10]:
missing_dates = list(range(1, 346))
for i in year2010days:
        missing_dates.remove(int(i))

print("A number of missing days in 2010 is {}. Here they are:".format(len(missing_dates)))
missing_dates.sort()
first_day_2010 = date(2010, 1, 1)
for i in missing_dates:
    print((first_day_2010 + timedelta(days=i-1)).strftime("%A %d, %B %Y"))

print("The last day of recording is {}.".format((first_day_2010 + timedelta(days=344)).strftime("%A %d, %B %Y")))
A number of missing days in 2010 is 15. Here they are:
Tuesday 12, January 2010
Friday 12, February 2010
Friday 12, March 2010
Monday 12, April 2010
Wednesday 12, May 2010
Saturday 12, June 2010
Monday 12, July 2010
Thursday 12, August 2010
Sunday 12, September 2010
Tuesday 12, October 2010
Friday 12, November 2010
Saturday 27, November 2010
Sunday 28, November 2010
Monday 29, November 2010
Tuesday 30, November 2010
The last day of recording is Saturday 11, December 2010.

Something is strange with 12th day of a month. And a few days in the end of the November are missing.

What is the proportion with respect to the total number of days?

In [15]:
15/(16+365+366+365+330)
Out[15]:
0.010402219140083218

So we have 1% more of missing data, which is above declared 1.25% in the data description. Still it is not very much, unless we decide to investigate what happens each month of year 2010.

Conversion into Date and Time types.

It looks like our Date and Time columns do not have missing values, or the previous cells would fail. I will transform them into Python date and time types, correspondingly. In addition I can create a timestamp column.

In [11]:
power_consumption["Date"] = pd.to_datetime(power_consumption["Date"])
power_consumption["Time"] = pd.to_timedelta(power_consumption["Time"])
power_consumption["Timestamp"] = power_consumption["Date"] + power_consumption["Time"]

Missing values as NaN

I will calculate missing values for each column.

In [12]:
power_consumption.isnull().sum()
Out[12]:
Date                         0
Time                         0
Global_active_power          0
Global_reactive_power        0
Voltage                      0
Global_intensity             0
Sub_metering_1               0
Sub_metering_2               0
Sub_metering_3           25979
Timestamp                    0
dtype: int64

We see missing values in the last column.

Discovery of masked missing values and their consequent replacement with NaNs.

Now I need to change data types so I can use them as numeric values. I will start with numerical values.

In [13]:
cols = list(power_consumption.columns)
cols.remove('Date'), cols.remove('Time'), cols.remove('Timestamp'), cols.remove('Sub_metering_3')

for col in cols:
    power_consumption[col] = pd.to_numeric(power_consumption[col])
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
pandas\_libs\lib.pyx in pandas._libs.lib.maybe_convert_numeric()

ValueError: Unable to parse string "?"

During handling of the above exception, another exception occurred:

ValueError                                Traceback (most recent call last)
<ipython-input-13-79ed4a4d3d7b> in <module>
      3 
      4 for col in cols:
----> 5     power_consumption[col] = pd.to_numeric(power_consumption[col])

~\Miniconda3\lib\site-packages\pandas\core\tools\numeric.py in to_numeric(arg, errors, downcast)
    149             coerce_numeric = errors not in ("ignore", "raise")
    150             values = lib.maybe_convert_numeric(
--> 151                 values, set(), coerce_numeric=coerce_numeric
    152             )
    153 

pandas\_libs\lib.pyx in pandas._libs.lib.maybe_convert_numeric()

ValueError: Unable to parse string "?" at position 6839

I see a non-numerical value in the table, "?". It appears at row 6839. Let us look at the row.

In [16]:
power_consumption.iloc[6839, :]
Out[16]:
Date                     2006-12-21 00:00:00
Time                         0 days 11:23:00
Global_active_power                        ?
Global_reactive_power                      ?
Voltage                                    ?
Global_intensity                           ?
Sub_metering_1                             ?
Sub_metering_2                             ?
Sub_metering_3                           NaN
Timestamp                2006-12-21 11:23:00
Name: 6839, dtype: object

Alas, here are more missing values. They are masked as '?'. I would rather have NaN instead.

In [17]:
power_consumption = power_consumption.replace('?', np.NaN)
power_consumption.isnull().sum(), power_consumption.iloc[6839, :]
Out[17]:
(Date                         0
 Time                         0
 Global_active_power      25979
 Global_reactive_power    25979
 Voltage                  25979
 Global_intensity         25979
 Sub_metering_1           25979
 Sub_metering_2           25979
 Sub_metering_3           25979
 Timestamp                    0
 dtype: int64, Date                     2006-12-21 00:00:00
 Time                         0 days 11:23:00
 Global_active_power                      NaN
 Global_reactive_power                    NaN
 Voltage                                  NaN
 Global_intensity                         NaN
 Sub_metering_1                           NaN
 Sub_metering_2                           NaN
 Sub_metering_3                           NaN
 Timestamp                2006-12-21 11:23:00
 Name: 6839, dtype: object)

Let us see what is a proportion of rows with missing values.

In [18]:
25979/2075259
Out[18]:
0.012518437457686004

So these are 1.25% of missing values about which we read on the data set home page.

Conversion to numerical values.

At this point the rest of column types may be switched to numerical.

In [19]:
for col in cols:
    power_consumption[col] = pd.to_numeric(power_consumption[col])
power_consumption.dtypes
Out[19]:
Date                      datetime64[ns]
Time                     timedelta64[ns]
Global_active_power              float64
Global_reactive_power            float64
Voltage                          float64
Global_intensity                 float64
Sub_metering_1                   float64
Sub_metering_2                   float64
Sub_metering_3                   float64
Timestamp                 datetime64[ns]
dtype: object

Here is a final check of data:

In [20]:
power_consumption.dtypes
Out[20]:
Date                      datetime64[ns]
Time                     timedelta64[ns]
Global_active_power              float64
Global_reactive_power            float64
Voltage                          float64
Global_intensity                 float64
Sub_metering_1                   float64
Sub_metering_2                   float64
Sub_metering_3                   float64
Timestamp                 datetime64[ns]
dtype: object
In [21]:
power_consumption.head()
Out[21]:
Date Time Global_active_power Global_reactive_power Voltage Global_intensity Sub_metering_1 Sub_metering_2 Sub_metering_3 Timestamp
0 2006-12-16 17:24:00 4.216 0.418 234.84 18.4 0.0 1.0 17.0 2006-12-16 17:24:00
1 2006-12-16 17:25:00 5.360 0.436 233.63 23.0 0.0 1.0 16.0 2006-12-16 17:25:00
2 2006-12-16 17:26:00 5.374 0.498 233.29 23.0 0.0 2.0 17.0 2006-12-16 17:26:00
3 2006-12-16 17:27:00 5.388 0.502 233.74 23.0 0.0 1.0 17.0 2006-12-16 17:27:00
4 2006-12-16 17:28:00 3.666 0.528 235.68 15.8 0.0 1.0 17.0 2006-12-16 17:28:00
As we see not only data types were changed, but date format was changed from European to American. In the next notebook we will see plots with aggregated data.

Thursday, October 17, 2019

SQL practice sites

To maintain my SQL skills I regularly practice them. I use the following sites:

https://sqlbolt.com/

https://sqlzoo.net/

http://www.sql-ex.ru/

http://www.sql-tutorial.ru/ru/book_sql_tips_and_solutions_10.html
 http://sql-tutorial.ru/ru/book_computers_database.html

https://www.pgexercises.com/

http://www.sqlcourse.com/
 

online books with exercises

 http://rsdn.org/res/book/db/sqltraining.xml

not much explanation here, only forums:
https://www.hackerrank.com/domains/sql/select

Example of technical interview:
https://habr.com/ru/post/181033/

https://www.youth4work.com/onlinetalenttest/Test-SQL-Server

Friday, September 20, 2019

Practicing with the book "Spark: the Definitive Guide"

In a technical book club at meetup "San Diego Technology Immersion Group" we are reading a book about Spark. Here is a complete reference:
"Spark: The definitive guide" by Bill Chambers, Matei Zaharia(O'Reilly), Copyright 2018 Databricks, Inc., 978-1-491-91221-8
I was especially interested in Machine Learning part of the book.  Unfortunately I learned that code provided in the book github account has some missing lines comparatively with the book, and in addition some of it does not work. So I made my source files and notebooks for chapters 24, 25 and 26. They are here:
https://github.com/Mathemilda/SparkTheDefinitiveGuideBook/tree/master/Part_IV_AdvAn%26ML


Thursday, February 28, 2019

Online Ad selection: Upper Confidence Bound method and Thompson Sampling method

The post is devoted to selection of the most popular ad to display on a webpage to gather the most clicks, or A/B test. The rate at which the webpage visitors click on an ad is called a conversion rate for the ad. The considered methods allow to make such selection in real time, so the most popular ads are showed as early as possible.

Assume that we have several ads and a place on a webpage to show one of them. We can display them one by one, record all the clicks, analyze the results afterwards and figure out the most popular. But an ad display may be pricey.  It would be more efficient to estimate rates in real time and to display the most popular one as soon as rates can be compared. Especially if an ad leads to a page for a visitor to buy something. There are couple of method for such estimations: Upper Confidence Bound method and Thompson Sampling method.

The first one is based on an confidence interval concept which is studied in a Statistics course and has a good intuitive explanation. Roughly speaking a confidence interval is a numeric interval were our value is supposed to lie with some probability, usually 95%. (The real statistical definition is more technical and means not quite this, but in practice the above explanation is close enough.) During our ad displaying we can compute average rates at each step with corresponding confidence intervals and pick up for next display an ad with a highest upper confidence bound. For the picked ad its mean an interval get recalculated and as result the confidence interval shrinks a little. You can see how it happens in the video below. The intervals are colored by ad legend, and their upper bounds are the right sides of corresponding rectangles. Calculated means are marked by vertical lines.


The method has some drawbacks. It does not take into account that our rates must be between 0 and 1, so initial confidence intervals usually wider than the [0, 1] segment. It means that we waste some time on getting realistic values for our intervals. The worst thing is that if after a start of the displaying we throw in an additional ad then the process takes a lot of time to recover.
Here is another method which is more efficient, Thompson Sampling Method. We assume Beta distributions for each ad rate and instead of computing averages draw a random number in accordance with each distribution for every step. Beta distributions are restricted to [0, 1] segment, so we do not have to wait for our numbers to fall into it. With each data point the beta distribution curve appears closer to the conversion rate mean. There is a picture how it goes for one ad, with a blue vertical line marking a mean and the red line for a random value:
As you see since a random value has more probability to appear where our curve is higher, then it get closer and closer to its mean at each step. With each step an interval where a distribution curve appears above horizontal axis shrinks. You might view the interval as a confidence interval analogue.

When we have several ads we compare the drawn random values at every step and pick up an ad to display with the greatest value. Here how it works for a few ads (I dropped means to make picture more clear):


Plus it accommodates an additional ad in the middle of the process more easily, taking less time to recover.
 Do not hesitate to ask questions and point our mistakes!

Tuesday, November 6, 2018

San Diego Map for Average Water Bacteria Counts at Measurement Stations

I have been working with San Diego Water quality data project:
https://www.sandiegodata.org/2018/04/summer-water-quality-data-project/
Here are data sets:
https://data.sandiegodata.org/dataset?tags=water-project
Regretfully my complete works do not fit into the blog post (or even a few posts) because of a post size restriction (1 Mb). Here is a repository for it: https://github.com/san-diego-water-quality/MyaBakhova
In particular I created a number of maps. You can see below one of them, where sizes of station marks correlate to pollution amounts: