{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Days of the Week of each Month through the Years\n", "### [Album Shen](http://albumshen.com/)\n", "*Wednesday the 21st, November, 2018*\n", "\n", "I've been helping my 5th grade sister with her math league problems, and some brainteasers involving leap years came up. This sparked a seemingly (probably actually is) trivial question of how do the days of the week compare to one another in how often they appear in each month of our calendars? One would guess that over time the frequency of each weekday occurring for each month would more-or-less get evened out, right? (i.e. an equal number of mon, tues, wed, etc.) Let's find out by taking a look at how the months on our calendar actually cycle through the days of the week, and when/if the sequence comes back to itself. Some questions we may answer are:\n", "\n", "* Over time, will there be more Mondays in January than Tuesdays in January?\n", "* How many Wednesdays have there been in March since the start of the Gregorian calendar?\n", "* If I host an event every 5th Thursday in February, would I be hosting more events than if I were to do it every 5th Friday?\n", "\n", "There are a number of [existing algorithms that can determine the day of the week](https://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week) for any given date. But I wanted to see how the distribution of these days in each month of the year play out over the course of an entire Gregorian cycle. We'll be running a program to evaluating the frequency of each weekday as they've occur in each month throughout the years since our current calendar's conception.\n", "\n", "**Fun fact:** The first day of the [Gregorian calendar](https://en.wikipedia.org/wiki/Gregorian_calendar) was October 15, 1582. A *Friday*." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "# Setting up the libaries & functions we'll be using\n", "import pandas as pd\n", "from IPython.display import display_html, HTML\n", "from datetime import date, datetime, timedelta as td\n", "from plotly.grid_objs import Grid, Column\n", "import plotly.plotly as py\n", "import time\n", "\n", "# Dictionaries for dataframe column & row headers\n", "weekdays = {0:\"sun\", 1:\"mon\", 2:\"tues\", 3:\"wed\",\n", " 4:\"thurs\", 5:\"fri\", 6:\"sat\"}\n", "months = {0:\"jan\", 1:\"feb\", 2:\"mar\", 3:\"apr\",\n", " 4:\"may\", 5:\"jun\", 6:\"jul\", 7:\"aug\",\n", " 8:\"sept\", 9:\"oct\", 10:\"nov\", 11:\"dec\"}\n", "ordinal = {0:\"1st\", 1:\"2nd\", 2:\"3rd\", 3:\"4th\",\n", " 4:\"5th\", 5:\"6th\", 6:\"7th\"}\n", "\n", "# Print the dates + weekday information given the\n", "# intervals of days from a specified origin date\n", "def printWeekDays(daysFrom, originDate):\n", " DayOnes = pd.to_datetime(daysFrom, unit='D', origin=pd.Timestamp(originDate))\n", " for day in DayOnes:\n", " print (day.strftime(\"%Y %b %d: %A (Day %w)\"))\n", "\n", "# Display dataframes side-by-side with their names on top\n", "def disp_dfs(*args):\n", " html_str = ''\n", " for df in args:\n", " html_str += '
'\\\n", " '

'\\\n", " +df.name+'

'\\\n", " +df.to_html()+'
'\n", " display_html(html_str.replace('table', 'table style=display:inline'), raw=True)\n", " \n", "# Date ranges using datetime dates \"date(%Y,%m,%d)\" as input\n", "def dateRange(start_date, end_date):\n", " for n in range(int ((end_date - start_date).days)):\n", " yield start_date + td(n)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Weekdays & Leap Cycles\n", "\n", "How long does it take for a given date to cycle back and coincide on the same day of the week again?\n", "\n", "The day of the week of any given date shifts 1 day for each nonleap year, and 2 years forward for each leap year." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2000 Jan 01: Saturday (Day 6)\n", "2001 Jan 01: Monday (Day 1)\n", "2002 Jan 01: Tuesday (Day 2)\n", "2003 Jan 01: Wednesday (Day 3)\n", "2004 Jan 01: Thursday (Day 4)\n" ] } ], "source": [ "# What a difference 4 years make\n", "daysFrom = [0, 365+1, 365*2+1, 365*3+1, 365*4+1]\n", "printWeekDays(daysFrom, '2000-1-1')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Over the course of each leap year interval, the total shift is by 5 (or -2) weekdays.\n", "\n", "So after 7 intervals (4*7 = 28 years), we should be back to the same day of the week on that date of the year." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2000 Jan 01: Saturday (Day 6)\n", "2004 Jan 01: Thursday (Day 4)\n", "2008 Jan 01: Tuesday (Day 2)\n", "2012 Jan 01: Sunday (Day 0)\n", "2016 Jan 01: Friday (Day 5)\n", "2020 Jan 01: Wednesday (Day 3)\n", "2024 Jan 01: Monday (Day 1)\n", "2028 Jan 01: Saturday (Day 6)\n" ] } ], "source": [ "# 7 leap cycles\n", "daysFrom = [0] * 8\n", "for day in range(len(daysFrom)):\n", " daysFrom[day] = 1461*day\n", "printWeekDays(daysFrom, '2000-1-1')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### The Lesser Known Century Rule\n", "28 day intervals maintain the same day of the week, but this does not account for the fact that there's an additional adjustment such that every century year\n", "that is not divisible by 400 is not a leap year so we start to see a drift and overcount by a day in the date, if we iterate every 28 years, as we pass such centuries." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2000 Jan 01: Saturday (Day 6)\n", "2028 Jan 01: Saturday (Day 6)\n", "2056 Jan 01: Saturday (Day 6)\n", "2084 Jan 01: Saturday (Day 6)\n", "2112 Jan 02: Saturday (Day 6)\n", "2140 Jan 02: Saturday (Day 6)\n", "2168 Jan 02: Saturday (Day 6)\n", "2196 Jan 02: Saturday (Day 6)\n", "2224 Jan 03: Saturday (Day 6)\n", "2252 Jan 03: Saturday (Day 6)\n" ] } ], "source": [ "# Drifting through the centuries\n", "daysFrom = [0] * 10\n", "for day in range(len(daysFrom)):\n", " daysFrom[day] = 1461*7*day\n", "printWeekDays(daysFrom, '2000-1-1')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A full Gregorian calendar cycle is 400 years, with 3 leap years omitted because they are century years nondivisible by 400.\n", "\n", "The total number of days in a full Gregorian cycle is 146097 = (400 yr * 365 days/yr) + 97 days from leap years." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1700 Jan 01: Friday (Day 5)\n", "2100 Jan 01: Friday (Day 5)\n" ] } ], "source": [ "# Full Greg\n", "daysFrom = [0, 146097]\n", "printWeekDays(daysFrom, '1700-1-1')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "146097 days is divisible by 7, so we see that every 400 years we are back to the day of the week in which we started, on the date of the calendar in which we started.\n", "\n", "But 400 years is not divisible by the 7 days of the week, and because days of the month other than nonleap year Feb have a number of days nondivisible by 7 (i.e. 29, 30, 31), there will be extra counts for the first one to three days of the week that that month began on. So this means we should expect an unequal distribution of the frequency of days of the week for each month." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/html": [ "

Distribution of Days in Each Month by Order of Appearance in Non-Leap Years

\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
1st2nd3rd4th5th6th7th
jan5554444
feb4444444
mar5554444
apr5544444
may5554444
jun5544444
jul5554444
aug5554444
sept5544444
oct5554444
nov5544444
dec5554444

Distribution of Days in Each Month by Order of Appearance in Leap Years

\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
1st2nd3rd4th5th6th7th
jan5554444
feb5444444
mar5554444
apr5544444
may5554444
jun5544444
jul5554444
aug5554444
sept5544444
oct5554444
nov5544444
dec5554444
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Number of days in each month for each month\n", "nonleap = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]\n", "leap = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]\n", "\n", "# Number of days shifted month-to-month\n", "nonShift = [0]*12\n", "leapShift = [0]*12\n", "\n", "# Dataframes for each type of year's distribution.\n", "# Every month has 28 or more days, so we can start with\n", "# at at least 4 counts for each day of the week.\n", "nonDist = pd.DataFrame([[4]*7]*12)\n", "nonDist.name = 'Distribution of Days in Each Month by Order\\\n", " of Appearance in Non-Leap Years'\n", "leapDist = pd.DataFrame([[4]*7]*12)\n", "leapDist.name = 'Distribution of Days in Each Month by Order\\\n", " of Appearance in Leap Years'\n", "\n", "# Then we can add in extra days depending on how many days\n", "# over 28 each month has.\n", "for month in range(0,12):\n", " nonShift[month] = nonleap[month]%7\n", " for extraDay in range(0, nonShift[month]):\n", " nonDist.at[month, extraDay] = 5\n", " leapShift[month] = leap[month]%7\n", " for extraDay in range(0, leapShift[month]):\n", " leapDist.at[month, extraDay] = 5\n", " \n", "nonDist.rename(columns=ordinal, index=months, inplace=True)\n", "leapDist.rename(columns=ordinal, index=months, inplace=True)\n", "disp_dfs(nonDist, leapDist)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### The Distribution of Days of the Week for Each Month Over 400 Years\n", "\n", "If we were 5th graders competing in a pencil + paper math contest, we could use the tables above, count the number of nonleap and leap years, and make adjustments for which weekdays begin on which year.\n", "\n", "But the easiest way to get this distribution would be to and iterate through a period of 400 years. On a computer it only takes about a minute." ] }, { "cell_type": "code", "execution_count": 67, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'https://plot.ly/~album/114/'" ] }, "execution_count": 67, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Initialize blank dataframe\n", "counts = pd.DataFrame([[0]*7]*12)\n", "counts.rename(columns=weekdays, index=months, inplace=True)\n", "\n", "# Count range (end_date non-inclusive)\n", "start_date = date(1582,10,15)\n", "end_date = date.today()\n", "\n", "# Columns for making grid\n", "yList = []\n", "mList = list(months.values())\n", "wList = list(weekdays.values())\n", "current_columns = []\n", "\n", "# Adds a column every year\n", "def addColumns(columns, wList, y):\n", " for i in wList:\n", " y_col_name = '{year}_{weekday}_{header}'.format(year=y, weekday=i, header='year')\n", " y_col = Column((list([y]*12)), y_col_name)\n", " columns.append(y_col)\n", " mListInt = [(j*10+(1.5*wList.index(i))) for j in range(0, 12)]\n", " m_col_name = '{year}_{weekday}_{header}'.format(year=y, weekday=i, header='month')\n", " m_col = Column(mListInt, m_col_name)\n", " columns.append(m_col)\n", " c_col_name = '{year}_{weekday}_{header}'.format(year=y, weekday=i, header='count')\n", " c_col = Column(counts[i].tolist(), c_col_name)\n", " columns.append(c_col)\n", "\n", "# Let's count\n", "for single_date in dateRange(start_date, end_date):\n", " # Updates grid/csv on new year's day\n", " if (single_date.strftime('%m %d')=='01 01') and (int(single_date.strftime('%y'))%4==0):\n", " y = int(single_date.strftime('%Y'))-1\n", " addColumns(current_columns, wList, y)\n", " yList.append(str(int(single_date.strftime('%Y'))-1))\n", " # Updates count in dataframe\n", " m = (1 * int(single_date.strftime(\"%m\")))-1\n", " w = int(single_date.strftime(\"%w\"))\n", " counts.iloc[m, w] += 1\n", "\n", "# Update if end date not on new year's day\n", "if (end_date.strftime('%m %d')!='01 01'):\n", " addColumns(current_columns, wList, end_date.strftime('%Y'))\n", " yList.append(str(int(single_date.strftime('%Y'))))\n", " \n", "# Upload grid to plotly\n", "countGrid = Grid(current_columns)\n", "url = py.grid_ops.upload(countGrid, 'weekday_counter_1582_2018_grid'+str(time.time()), auto_open=False)\n", "url" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "I ran and stored the final dataframe for a full Gregorian cycle to '400_years.csv'. Drumroll please." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Counted 146097 days\n" ] }, { "data": { "text/html": [ "

Number of Weekdays That Occur in Each Month in Each Gregorian Calendar Cycle

\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
sunmontueswedthursfrisat
jan1772177017721771177217721771
feb1613161516131615161316141614
mar1772177117721770177217711772
apr1714171517141715171417141714
may1772177017721771177217721771
jun1714171517141714171417141715
jul1772177117721772177117721770
aug1771177217701772177117721772
sept1715171417151714171417141714
oct1770177217711772177217711772
nov1715171417141714171417151714
dec1771177217721771177217701772
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "gregCycle = pd.read_csv('400_years.csv', index_col=0)\n", "gregCycle.name = 'Number of Weekdays That Occur in Each Month in Each Gregorian Calendar Cycle'\n", "\n", "# To check that we did in fact count all the days\n", "print (\"Counted \" + str(gregCycle.values.sum()) + \" days\")\n", "\n", "disp_dfs(gregCycle)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Animated Graph" ] }, { "cell_type": "code", "execution_count": 68, "metadata": {}, "outputs": [], "source": [ "# Figure\n", "figure = {\n", " 'data': [],\n", " 'layout': {},\n", " 'frames': [],\n", " 'config': {'scrollzoom': True}\n", "}\n", "\n", "# Fill in most of layout\n", "figure['layout']['xaxis'] = {'title': 'Month', 'gridcolor': '#FFFFFF', 'range': [-2, 120], 'zeroline': False,\n", " 'tickvals': [(i*10+3) for i in range(0, 12)], 'ticktext': mList}\n", "figure['layout']['yaxis'] = {'title': 'Days Counted', 'type': 'lin', 'range': [0, 450], 'gridcolor': '#FFFFFF', 'autorange':False}\n", "figure['layout']['title'] = 'Counting the Days of Each Weekday in Each Month'\n", "figure['layout']['hovermode'] = 'x'\n", "figure['layout']['plot_bgcolor'] = 'rgb(223, 232, 243)'\n", "figure['layout']['autosize'] = True\n", "\n", "# Year Slider\n", "sliders_dict = {\n", " 'active': 0,\n", " 'yanchor': 'top',\n", " 'xanchor': 'left',\n", " 'currentvalue': {\n", " 'font': {'size': 20},\n", " 'prefix': 'Year:',\n", " 'visible': True,\n", " 'xanchor': 'right'\n", " },\n", " 'transition': {'duration': 100, 'easing': 'cubic-in-out'},\n", " 'pad': {'b': 10, 't': 50},\n", " 'len': 0.9,\n", " 'x': 0.1,\n", " 'y': 0,\n", " 'steps': [],\n", "}\n", "\n", "# Play & Pause\n", "figure['layout']['updatemenus'] = [\n", " {\n", " 'buttons': [\n", " {\n", " 'args': [None, {'frame': {'duration': 300, 'redraw': False},\n", " 'fromcurrent': True, 'transition': {'duration': 400, 'easing': 'quadratic-in-out'}}],\n", " 'label': 'Play',\n", " 'method': 'animate'\n", " },\n", " {\n", " 'args': [[None], {'frame': {'duration': 0, 'redraw': False}, 'mode': 'immediate',\n", " 'transition': {'duration': 0}}],\n", " 'label': 'Pause',\n", " 'method': 'animate'\n", " },\n", " {\n", " 'args': [{'yaxis.autorange': True, 'xaxis.autorange': True}],\n", " 'label': 'Rescale',\n", " 'method': 'relayout'\n", " },\n", " ],\n", " 'direction': 'left',\n", " 'pad': {'r': 10, 't': 87},\n", " 'showactive': False,\n", " 'type': 'buttons',\n", " 'x': 0.1,\n", " 'xanchor': 'right',\n", " 'y': 0,\n", " 'yanchor': 'top'\n", " }\n", "]\n", "\n", "# Custom marker styles\n", "color = {\n", " 'sun': 'rgb(250, 249, 20)', 'mon': 'rgb(250, 20, 5)', 'tues': 'rgb(50, 170, 255)', 'wed': 'rgb(222, 182, 0)',\n", " 'thurs': 'rgb(90, 110, 250)', 'fri': 'rgb(115, 211, 143)', 'sat': 'rgb(20, 211, 43)'\n", "}\n", "symbol = {\n", " 'sun': 'circle-open-dot', 'mon': 'square-cross', 'tues': 'star-diamond', 'wed': 'hexagram',\n", " 'thurs': 'diamond', 'fri': 'pentagon', 'sat': 'star'\n", "}\n", "line_color = {\n", " 'sun': 'rgb(250, 99, 220)', 'mon': 'rgb(230, 99, 250)', 'tues': 'rgb(99, 110, 250)', 'wed': 'rgb(222, 222, 44)',\n", " 'thurs': 'rgb(50, 170, 255)', 'fri': 'rgb(115, 211, 143)', 'sat': 'rgb(220, 111, 243)'\n", "}\n", "gradient_color = {\n", " 'sun': 'rgb(0, 0, 0)', 'mon': 'rgb(230, 20, 0)', 'tues': 'rgb(22, 55, 250)', 'wed': 'rgb(222, 140, 0)',\n", " 'thurs': 'rgb(50, 170, 255)', 'fri': 'rgb(115, 211, 143)', 'sat': 'rgb(22, 22, 111)'\n", "}\n", "gradient_type = {\n", " 'sun': 'radial', 'mon': 'horizontal', 'tues': 'vertical', 'wed': 'horizontal',\n", " 'thurs': 'vertical', 'fri': 'radial', 'sat': 'radial'\n", "}\n", "set_size = 6\n", "set_opacity = 0.6\n", "set_line_width = 3\n", "\n", "# Import data from grid\n", "col_name_template = '{year}_{weekday}_{header}'\n", "year = yList[0]\n", "for day in wList:\n", " data_dict = {\n", " 'xsrc': countGrid.get_column_reference(col_name_template.format(\n", " year=year, weekday=day, header='month'\n", " )),\n", " 'ysrc': countGrid.get_column_reference(col_name_template.format(\n", " year=year, weekday=day, header='count'\n", " )),\n", " 'mode': 'markers',\n", " 'textsrc': countGrid.get_column_reference(col_name_template.format(\n", " year=year, weekday=day, header='month'\n", " )),\n", " 'hoverinfo': 'y+name',\n", " 'marker': {\n", " 'size': set_size,\n", " 'symbol': symbol[day],\n", " 'color': color[day],\n", " 'opacity': set_opacity,\n", " 'line': {'color': line_color[day], 'width': set_line_width },\n", " 'gradient': {'color': gradient_color[day], 'type':gradient_type[day]}\n", " },\n", " 'name': day\n", " }\n", " figure['data'].append(data_dict)\n", "\n", "# Updating frames\n", "for year in yList:\n", " frame = {'data': [], 'name': str(year), 'layout':[]}\n", " for day in wList:\n", " data_dict = {\n", " 'xsrc': countGrid.get_column_reference(col_name_template.format(\n", " year=year, weekday=day, header='month'\n", " )),\n", " 'ysrc': countGrid.get_column_reference(col_name_template.format(\n", " year=year, weekday=day, header='count'\n", " )),\n", " 'mode': 'markers',\n", " 'textsrc': countGrid.get_column_reference(col_name_template.format(\n", " year=year, weekday=day, header='month'\n", " )),\n", " 'marker': {\n", " 'size': set_size,\n", " 'symbol': symbol[day],\n", " 'color': color[day],\n", " 'opacity': set_opacity,\n", " 'line': {'color': line_color[day], 'width': set_line_width },\n", " 'gradient': {'color': gradient_color[day], 'type':'radial'}\n", " },\n", " 'name': day,\n", " }\n", " frame['data'].append(data_dict)\n", " layout_dict = {\n", " 'yaxis': {'autorange': True}\n", " }\n", " frame['layout'].append(layout_dict)\n", " figure['frames'].append(frame)\n", "\n", " slider_step = {'args': [\n", " [year],\n", " {'frame': {'duration': 30, 'redraw': False},\n", " 'mode': 'immediate',\n", " 'transition': {'duration': 10}}\n", " ],\n", " 'label': year,\n", " 'method': 'animate'}\n", " sliders_dict['steps'].append(slider_step)\n", " figure['layout']['sliders'] = [sliders_dict]\n", "\n", "# Default home zoom\n", "yMin = (int(min(yList)) - 1582) * 4\n", "yMax = ((int(max(yList)) - int(min(yList))) * 4.5) + yMin\n", "figure['layout']['yaxis']['range'] = [yMin, yMax]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Have you ever seen those [marble racing videos](https://www.youtube.com/watch?v=iG_jGYqsZZo)?**\n", "\n", "Here are some dots to represent the days of the week for each month. Press \"play\" to watch them race from the start of the Gregorian calendar to present day.\n", "\n", "*(Pressing the \"Rescale\" button zooms in on the action. I haven't figured out how to get the scale to autoupdate with each frame yet-- if you know, please let me know! You might have to keep clicking rescale to follow the movement, or hover over the top of the graph and use the pan tool. The house-shaped icon will reset the axes.)*" ] }, { "cell_type": "code", "execution_count": 69, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "execution_count": 69, "metadata": {}, "output_type": "execute_result" } ], "source": [ "py.icreate_animations(figure, 'weekday-counter-1582-2018'+str(time.time()))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Here are some takeaways about each month for a full Gregorian Cycle:**\n", "* **Jan:** least frequent day is Mon.\n", "* **Feb:** has 99-159 fewer of each day than the other months.\n", "* **Mar:** least frequent day is Wed.\n", "* **May:** least frequent day is Mon.\n", "* **Jul:** least frequent day is Sat.\n", "* **Aug:** least frequent day is Tues. \n", " *With 1772 Fridays and 1772 Saturdays, August is the month that has the most weekend days!!*\n", "* **Oct:** least frequent day is Sun.\n", "* **Dec:** least frequent day is Fri.\n", "\n", "**Highs & Lows**\n", "* Highest count for any weekday: 1772\n", "* Lowest count for non-Feb months: 1714\n", "* Range for Feb days: 1613-1615\n", "\n", "So there you have it. You can file that under ~~useful~~ information." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Further Reading\n", "\n", "I had written a similar shorter program in js on Feb 24, 2016, which I later learned coincided as the 434th anniversary of the papal bull known as the *Inter gravissimas*, issused by Pope_Greg13, which gave us the calendar that we have all come to know and know.\n", "\n", "If you want to go down this rabbit hole some more, here are some links related to time-related adjustments we face as a consequence of living on this planet:\n", "* **Gregorian Calendar:** https://en.wikipedia.org/wiki/Gregorian_calendar\n", "* **Inter Gravissimas:** https://en.wikisource.org/?curid=566140\n", "* **Perpetual Calendars:** https://en.wikipedia.org/wiki/Perpetual_calendar\n", "* **Determination of the day of the week:** https://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week\n", "* **\"How to Figure Out the Day of the Week For Any Date Ever [with just one hand]\":** https://www.youtube.com/watch?v=714LTMNJy5M\n", "* **Doomsday Rule:** https://en.wikipedia.org/wiki/Doomsday_rule\n", "* **\"Leap years: we can do better\":** https://www.youtube.com/watch?v=qkt_wmRKYNQ\n", "* **Leap Seconds:** https://en.wikipedia.org/wiki/Leap_second\n", "* **Leap Smears:** https://www.webopedia.com/TERM/L/leap-smear.html\n", "* **Google Smears:** https://developers.google.com/time/smear#othersmears\n", "* **Leap second Linux server crashes:** https://serverfault.com/questions/403732/anyone-else-experiencing-high-rates-of-linux-server-crashes-during-a-leap-second\n", "* **Unix Time:** https://en.wikipedia.org/wiki/Unix_time\n", "* **Year 2038 Problem:** https://en.wikipedia.org/wiki/Year_2038_problem\n", "* **Names of the Days of the Week:** https://en.wikipedia.org/wiki/Names_of_the_days_of_the_week\n", "* **Lightning calculation and other \"mathemagic\" | Arthur Benjamin:** https://www.youtube.com/watch?v=M4vqr3_ROIk\n", "\n", "*Thanks to Nick Lanam, Ethan McIntyre for pointing me in the direction of some more helpful links.*" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.0" } }, "nbformat": 4, "nbformat_minor": 2 }