Telegraph Lines
Largely abandoned and forgotten, the telegraph lines poll make a double cross in the air as the snow falls around them.
Sunday December 1, 2019 — Albany County Rail TrailLargely abandoned and forgotten, the telegraph lines poll make a double cross in the air as the snow falls around them.
Sunday December 1, 2019 — Albany County Rail TrailWith Omricon being in the news, South Africa is on many people's minds. A picture of tip of Africa earlier today. Taken from the Suomi NPP Visible Infrared Imaging Radiometer.
The World Health Organization is warning that the new omicron variant of the coronavirus poses a "very high" global risk because of the possibility that it spreads more easily and might resist vaccines and immunity in people who were infected with previous strains.
In a technical brief issued Sunday, the WHO warned its 194 member states that the new variant's numerous mutations "may confer immune escape potential and possibly transmissibility advantage," and as a result "the likelihood of potential further spread of omicron at the global level is high."
Below is the code I am using to generate the crunched numbers for Albany County, for example my 2021 Albany County Election Spreadsheet. Text is converted using pdftotext -layout 2021GeneralRecanvass.txt which converts the PDF to a structured text file for easier parsing. The Excel Writer library is used for extensive formatting of the file for easy reading and parsing. View this as a separate page for syntax highlighting.
from io import StringIO
import re
import pandas as pd
import numpy as np
# paths
# electionResultText = '/home/andy/Desktop/2021GeneralRecanvass.txt'
# enrollmentXLS = '/home/andy/enroll/nov21-Enrollment/AlbanyED_nov21.xlsx'
# outputPath = '/tmp/2021_albany_county_races.xlsx'
# electionResultText = '/home/andy/Documents/GIS.Data/election.districts/2020GeneralRecanvass.txt'
# enrollmentXLS = '/home/andy/enroll/nov20-Enrollment/AlbanyED_nov20.xlsx'
# outputPath = '/tmp/2020_albany_county_races.xlsx'
electionResultText = '/home/andy/Documents/GIS.Data/election.districts/2019'
enrollmentXLS = '/home/andy/enroll/nov19-Enrollment/AlbanyED_nov19.xlsx'
outputPath = '/tmp/2018_albany_county_races.xlsx'
# number to excel column letter
def letter(colNum):
import math
if colNum > 26:
return chr(ord('@')+math.floor(colNum/26))+chr(ord('@')+colNum%26)
else:
return chr(ord('@')+colNum)
pd.set_option("display.max_rows", None)
pd.set_option("display.max_columns", None) # show everything when previewing
with open(electionResultText) as f:
data = f.read()
enroll = pd.read_excel(enrollmentXLS, header=4)
enroll = enroll[(enroll['STATUS']=='Active')][['ELECTION DIST', 'TOTAL']].dropna()
enroll['ELECTION DIST'] = enroll['ELECTION DIST'].astype(str).str.replace(' ',' ')
enroll['ELECTION DIST']=enroll['ELECTION DIST'].str.strip()
enroll['Enrollment'] = enroll['TOTAL']
enroll.drop(labels=['TOTAL'], axis=1, inplace=True)
# array containing election result dataframes
er = {}
# split each race which is divided by ten or more equal signs
for raceData in re.split('={10,}',data):
# blank out old data frame
df = None
# split lines
rows = raceData.split('\n')
race = ""
candidates = {}
startAt = 0
for i, line in enumerate(rows):
# find race name
if re.search('VOTES\s*?PERCENT', line):
race = rows[i+1]
race = race.replace(' ',' ')
race = race.rstrip()
#print(race)
# find candidate names
for result in re.findall('(\d\d)\s*?=\s*?(\w.*?)\d', line):
candidates[int(result[0])] = result[1].rstrip()
# find start at location for CSV reader
if re.findall('-{5,}', line):
startAt = i+3
break
# skips enrollment stats, as the don't have candidates
if not candidates or not 2 in candidates:
continue
if not race:
continue
#print(race)
df = pd.read_csv(
StringIO(raceData),
header=None,
skiprows=startAt,
sep='(?<=\d)\s{1,}(?=\d)',
engine='python',
on_bad_lines='warn')
df=df[df[0].str.contains('^\d{4}').fillna(False)] # ONLY ROWS STARTING WITH 4-DIGIT ED CODE
df.reset_index(drop=True, inplace=True) # reset index so we can use it in formulas
df=df.rename(candidates,axis=1) # rename columns
df.iloc[:,1:]=df.iloc[:,1:].apply(pd.to_numeric) # make sure all columns are numeric
df.iloc[:,1:]=df.iloc[:,1:].convert_dtypes('int32') # cast columns to int32
# crunching
df.insert(1, 'Ballot', df.iloc[:,1:].convert_dtypes('int32').sum(axis=1)) # add total column
df.insert(2, 'Blanks', df['OVER VOTES'].convert_dtypes('int32')) # add blank
df.drop(labels=['OVER VOTES'], axis=1, inplace=True)
df['Blanks']+=df['UNDER VOTES'].convert_dtypes('int32') # add under votes
df.drop(labels=['UNDER VOTES'], axis=1, inplace=True)
df.insert(3, 'Canvas', '=F'+(df.index+2).map(str)+'-G'+(df.index+2).map(str))
df.insert(4, 'TO %', '=(F'+(df.index+2).map(str)+'/E'+(df.index+2).map(str)+')')
df.insert(5, 'DO %', '=(G'+(df.index+2).map(str)+'/F'+(df.index+2).map(str)+')')
# create check columns
df['CHECK'] = '=F'+(df.index+2).map(str)+'-G'+(df.index+2).map(str)
df['CHECK %'] = '=0'
# add percent columns
for i, col in enumerate(df.columns[6:-2]):
try:
# check columns
if (len(df.columns)-i-2) > 0:
# add to check column
df['CHECK'] += '-'+letter(i+i+11)+(df.index+2).map(str)
# add check percent
df['CHECK %'] += '+'+letter(i+i+12)+(df.index+2).map(str)
# add percent
df.insert(i+i+7, col+' %', '=('+letter(i+i+11)+(df.index+2).map(str)+'/H'+(df.index+2).map(str)+')')
except:
pass
# add T W E Columns, drop combined field
df[['ED Code','Municipality','String','Ward','ED']]=df[0].str.extract('(\d\d\d\d)\s*?(.*?)(\s*?WARD\s*(\d*))?\s*?ED\s*(\d*)')
# temporary string used for data merge with enrollments
df['ELECTION DIST'] = df['Municipality'].str.strip().replace(' ',' ') + \
' '+df['Ward'].fillna(0).astype(str).str.zfill(3)+df['ED'].str.zfill(3).astype('str')
# merge on ED Key
df=df.merge(enroll, on='ELECTION DIST', how='left')
df=df.drop(0, axis=1)
df=df.drop(labels=['ELECTION DIST','String'],axis=1)
# move columns to proper order
cols = list(df.columns)
df = df[cols[-5:]+cols[:-5]]
df['Municipality']=df['Municipality'].str.title()
df['Municipality'] = df['Municipality'].str.strip().replace(' ',' ') # remove whitespace around muni column
# array with election result dataframes
er[race]=df.fillna(0)
# write file
ew = pd.ExcelWriter(outputPath)
for i, race in enumerate(er):
raceStr = str(i+1)+' '+race
if (len(race)>28):
raceStr = str(i+1)+' '+race[:10] + '...' + race[-10:]
# disble header, manually write, as pre-defined headers can't be formatted
er[race].fillna(0).to_excel(ew,sheet_name=raceStr, index=False)
headForm = ew.book.add_format({'text_wrap': 1, 'font_family': 'Arial', 'bold': True ,
'valign': 'vcenter', 'align': 'center', 'bg_color': '#CCCCCC'})
for colnum, value in enumerate(er[race].columns.values):
ew.sheets[raceStr].write(0, colnum, value, headForm)
ew.sheets[raceStr].set_row(0,50)
# set column width for all columns to 20, freeze panes
bodyForm = ew.book.add_format({'text_wrap': 1, 'font_family': 'Arial', 'num_format': '#,##0', 'valign': 'vcenter', 'align': 'right'})
bodyPerForm = ew.book.add_format({'text_wrap': 1, 'font_family': 'Arial', 'num_format': '0.0%', 'valign': 'vcenter', 'align': 'right'})
muniForm = ew.book.add_format({'text_wrap': 1, 'font_family': 'Arial', 'valign': 'vcenter', 'align': 'center'})
ew.sheets[raceStr].set_column('A1:A9999', 6, bodyForm)
ew.sheets[raceStr].set_column('B1:B9999', 15, muniForm)
ew.sheets[raceStr].set_column('C1:D9999', 4, bodyForm)
ew.sheets[raceStr].set_column('E1:H9999', 10, bodyForm)
ew.sheets[raceStr].set_column('I1:J9999', 10, bodyPerForm)
for colNum in range(11,40):
if colNum%2:
ew.sheets[raceStr].set_column(letter(colNum)+'1:'+letter(colNum)+'9999', 12, bodyForm)
else:
ew.sheets[raceStr].set_column(letter(colNum)+'1:'+letter(colNum)+'9999', 12, bodyPerForm)
ew.sheets[raceStr].freeze_panes('E2')
ew.save()
Not a lot of snow ahead but generally cool and December like. I guess that’s to be expected this time of year. The weekend doesn’t look that bad but it’s too early to make plans. If I headed up to the Berskires I could use my skis 🎿. Or maybe the Adirondacks, depends on how much snow and if my truck is ready by then.
Truth be told though at this point I plan to basically hibernate, focus on work, hike out to Five Rivers 🐦 and count down the days to spring. Some smaller trips, but the days are short, the nights are long and it’s kind of cold. Maybe though definately thinking about an end of the year trip to kick off the New Year. NYE is a Friday this year.
| Today. Feels like … December 22nd. |
|
A slight chance of snow showers between noon and 4pm. Mostly cloudy.
North wind 3 to 8 mph. Chance of precipitation is 20%.
|
36 degrees | 4:23 sunset |
|
| Tonight. Feels like … December 21st. |
|
Partly cloudy.
Northwest wind 5 to 7 mph becoming calm in the evening.
|
21 degrees | 7:05 sunrise |
|
| Tuesday. Feels like … December 15th. |
|
Partly sunny.
South wind 5 to 8 mph.
|
38 degrees | 13 max wind chill | 4:23 sunset |
| Tuesday Night. Feels like … November 26th. |
|
Mostly cloudy.
South wind around 8 mph.
|
28 degrees | 7:06 sunrise |
|
| Wednesday. Feels like … December 3rd. |
|
Partly sunny.
Southwest wind 7 to 9 mph.
|
42 degrees | 4:23 sunset |
|
| Wednesday Night. Feels like … November 17th. |
|
A chance of snow showers after 1am. Mostly cloudy.
Chance of precipitation is 30%.
|
31 degrees | 7:07 sunrise |
|
| Thursday. Feels like … November 25th. |
|
Snow showers likely before 1pm, then a chance of rain showers. Mostly cloudy.
Chance of precipitation is 60%.
|
45 degrees | 4:22 sunset |
|
| Thursday Night. Feels like … November 13th. |
|
Mostly cloudy.
|
32 degrees | 7:08 sunrise |
|
| Friday. Feels like … December 15th. |
|
Mostly sunny.
|
38 degrees | 4:22 sunset |
|
| Friday Night. Feels like … November 30th. |
|
A chance of snow showers after 1am. Mostly cloudy.
Chance of precipitation is 30%.
|
27 degrees | 7:09 sunrise |
|
| Saturday. Feels like … December 18th. |
|
A chance of snow showers. Partly sunny.
Chance of precipitation is 30%.
|
37 degrees | 4:22 sunset |
|
| Saturday Night. Feels like … December 17th. |
|
Partly cloudy.
|
22 degrees | 7:10 sunrise |
|
| Sunday. Feels like … December 31st. |
|
Partly sunny.
|
34 degrees | 4:22 sunset |