29 December 2008

Pachelbel lives on.

Great recent song by The Fratellis on their 2006 album Costello Music, "Ole Black 'N' Blue Eyes."

The first thing that jumped out at me was how closely the progression of the song mirrored the Pachelbel Canon in D's ground bass (though transposed to B-flat):
(Not sure if the right hand part matches the actual guitar parts used in the piece, but they're close enough for illustrative purposes).

But better than seeing similarities, I thought a little remix of the opening minute or so would show just how compatible the two pieces were. Enjoy.

And the original...

05 October 2008

Overwrite IE Favorites with Firefox Bookmarks


I love Firefox, but on Windows IE has one big advantage over it: it's made by the same people who made the OS. This lets IE access its favorites from all sorts of places, not just from the browser. I'm a task-oriented person rather than application-oriented: I tend to think "Gotta pay the bills now" not "Gotta open up the browser and then do things that require a browser, such as bill pay." IE 4.0+ (and Windows98) was oriented towards people like me, by putting the Favorites option in the start menu, every folder, etc. It never really caught on, and in fact the Favorites option was quietly removed from the start menu in either XP or Vista, though it can be re-enabled from the taskbar options.

There are a couple of ways to get your Firefox bookmarks in the Favorites. There are lots of bookmark synchronization programs, though they tend to need to be run manually--which is messy for me. There's also the PlainOlFavorites plug-in for Firefox which lets you just use IE favorites instead of Bookmarks. I loved this plug in for a few years, but I like FF's native Bookmark system enough that I wanted to make my own synchronization system.

Here's a little Python script I came up with. It copies YESTERDAY's Firefox backup over your Favorites (copying the current version would just have been too messy). It requires Python 2.6 or you'll need to install the "simplejson" module and replace all references to "json" with "simplejson" in the code. Do not run this if you use IE favorites: it will DELETE THEM ALL and overwrite them with Firefox favorites. You can comment out the "nukeFavorites()" line if you just want to add to your existing IE favorites. I plop this script into my Startup folder and it works great!


# bookmarks_json.py -- Michael Scott Cuthbert
import json
import copy
import sys
import os
import shutil

class BookmarkMaker(object):

startdir = os.environ["USERPROFILE"] + r'\Favorites'
internetShortcut = "[InternetShortcut]\nURL="

folderpath = os.environ["APPDATA"] + "\\Mozilla\\Firefox\\Profiles\\"
backuppath = r'\bookmarkbackups'

def nukeFavorites(self):
'''deletes everything in the Favorites dir -- do not use this if you update Favorites!'''
nukeem = os.listdir(self.startdir)
for thisfile in nukeem:
if thisfile != "desktop.ini":
thisFullFile = self.startdir + os.sep + thisfile
try:
os.remove(thisFullFile)
except OSError:
shutil.rmtree(thisFullFile)

def run(self):
stringFile = self.openRightFile()
self.parse(stringFile)

def openRightFile(self):
backupPath = self.backupPath()
theseFiles = os.listdir(backupPath);
theseFiles.sort(reverse=True)
for thisFile in theseFiles:
if thisFile.startswith('bookmarks-') and thisFile.endswith('.json'):
return self.openJson(backupPath + os.sep + thisFile)

def backupPath(self):
profile = self.mostRecentProfile(self.folderpath)
return profile + self.backuppath

def mostRecentProfile(self, profilesPath):
'''returns the most recently modified Profile in the profilesPath'''
newestName = ""
newestTime = 0

allDirs = os.listdir(profilesPath)
for thisDir in allDirs:
thisFullPath = profilesPath + os.sep + thisDir
try:
thisModTime = os.path.getmtime(thisFullPath + os.sep + "places.sqlite")
if thisModTime > newestTime:
newestTime = thisModTime
newestName = thisFullPath
except:
pass
return newestName

def openJson(self, jsonFile):
try:
myfile = open(jsonFile) #folderpath + '\\' + bookmarkfile)
except:
raise Exception("Could not open file " + jsonFile)
return myfile.read()

def parse(self, stringData):
self.jsonStuff = json.loads(stringData)
self.realBase = self.findRealBase(self.jsonStuff)
self.parseReally(self.realBase)

def findRealBase(self, searchIn):
return searchIn["children"][0]["children"]

def parseReally(self, startingPoint, currentDir = []):
for thing in startingPoint:
if thing.has_key("children"): ## Bookmark folder
tempdir = copy.copy(currentDir)
tempdir.append(thing["title"])
self.parseReally(thing["children"], tempdir)
elif thing.has_key("uri"): ## Bookmark
self.createFavorite(thing["uri"], os.sep.join(currentDir) + os.sep + thing["title"] + ".url")

def createFavorite(self, uri, filename):
shortcutOut = self.internetShortcut + uri
if not(filename.startswith(os.sep)):
filename = os.sep + filename
filename = self.startdir + filename
try:
self.recursiveMakeDir(filename)
filehandle = open(filename, "w")
filehandle.write(shortcutOut)
except:
pass

def recursiveMakeDir(self, filename):
if filename.count(os.sep) > 12:
raise Exception("Whoa, you sure you want to go so deep and make " + filename + "?")

parentDir = os.path.dirname(filename)
if os.path.exists(parentDir):
return True
else:
self.recursiveMakeDir(parentDir)
os.mkdir(parentDir)

if (__name__ == "__main__"):
bmm = BookmarkMaker()
bmm.nukeFavorites()
bmm.run()

20 September 2008

New mail script

I used to use this little program called "nfrm" (or frm -n) to tell me on log in to a unix system if I had new mail. Unfortunately what worked in the past no longer was keeping accurate lists in the world where I was also checking my mail via IMAP and the iPhone, where there's a lot of mail I've been informed (in some way) about, but have never actually read.

Here's a quick Perl script that I wrote which checks three folders (my inbox, a work-related box, and mailing lists) for new messages. It requires Mail::MboxParser (install via "cpan" then "install Mail::MboxParser" or have your sysadmin do it for you):

#!/usr/bin/perl

use Mail::MboxParser;
use Term::ReadKey;
use strict;

my ($wchar, $hchar, $wpixels, $hpixels) = GetTerminalSize();
my $available_space = $wchar - 13;
my $from_length = int($available_space * .33);
my $subject_length = int($available_space * .67);

my @mailboxes = @ARGV;
if (scalar(@mailboxes) == 0) {
@mailboxes = (["**** ", $ENV{'MAIL'}],
["NBER ", "/homes/nber/cuthbert/mail/nber"],
["Lists", "/homes/nber/cuthbert/mail/lists"]);
}

my $parseropts = {
enable_cache => 1,
enable_grep => 1,
cache_file_name => '/tmp/msc-mbox-cache-file',
};

foreach my $box (@mailboxes) {
next unless -e $box->[1];
my $mb = Mail::MboxParser->new($box->[1],
decode => 'ALL',
parseropts => $parseropts);
while (my $msg = $mb->next_message) {
if ($msg->header->{status} !~ /R/) {
my $name = $msg->from->{name};
if (!$name) { $name = $msg->from->{email} }
$name =~ s/\"//g;
$name =~ s/^\s+//;
$name = substr($name, 0, $from_length);

my $subject = $msg->header->{subject};
$subject = substr($subject, 0, $subject_length);

printf("%-8s %-${from_length}s %-${subject_length}s\n", $box->[0], $name, $subject);
}
}
}

14 September 2008

Sexist ol' folks! (maybe not so much)


I was reading Matt Bai's most recent post on the presidential campaign; not as good as his usual missives, but still basically entertaining (and informative I didn't realize he was white; I guess I assumed Bai as an Asain name). But I focused mainly on a graphic that the New York Times placed in a sidebar showing the percentage of Americans who believe that a woman will be elected president during their lifetimes. Another statistical graphic without any exegesis; when will the Times learn? Based on the main gist of the article, I suppose we are to conclude that younger people are more open to the idea of a female president, and, therefore, more likely to conclude that a woman will be elected. Of the under-45 voters who expressed an opinion (95%), 17% believed that a woman would not be elected in their lifetimes. On the other hand, of the over-65 voters with an opinion (83%), 47% believed that no woman would be elected while they lived.

But the young have something more important than open-mindedness that accounts for the statistical difference: life expectancy. Simply put, the under 45 voting crowd will witness many more elections, and, therefore many more chances of electing a female president. Let's plot this out. Given a probability X of a woman being elected in any election, the probability that a woman would NOT be elected after N elections (or 4N years) is:
(1-X)^N
The following chart gives the probability that a woman would not be elected given various values for X (left column) and N (top row):



Let's say that an "Under 45 voter" is 35 (a mean of 18-45 weighed toward the fact that younger people vote in lower percentages) and let's call an "Over 65 voter" 70 years old. It's obvious that a woman will not be elected president this year, so we'll say that the 70 year olds have, on average 3.5 more elections in their lifetime where a woman could be elected (I'm giving a them more longevity than you'd expect since there is some probability that McCain will be elected and die in his first term, giving a Mrs. President Palin. Though this poll was taken in June, after Hillary had dropped out but before McCain's choice of a female VP seemed likely). The 35-year-old voter has, on average, 10 more elections (I'm giving them a few fewer years on average to live because a 35 year old hasn't proved they won't die before 70). And we'll define "likely to become president" the standard way: over 50% chance. That is, if the voter thinking that it was likely had to bet with even odds, he or she would bet for a woman president being elected.

What can we extrapolate from the chart and formula? If a (rational) 35-year-old voter said that he thought it likely for a woman to be elected, he would need to believe that a woman has over a 7% chance of being elected in any given election, while a 70-year-old voter needs to believe that there's an 18% chance of a woman being elected.

A quick-and-dirty way of extrapolating from the results of the poll to the table is to say if 25% of people believe X is likely, we should look at the point in the table where X is 25% likely to happen. (There are better ways depending on what distribution of beliefs you think voters have, but this should work well enough for now). So since 47% of over-65 voters don't believe a woman will be elected in the next 3.5 elections, we can suppose that they believe women have a 20% chance of being elected any given election.

What about the younger crowds? 17% of the under-45 crowd believe that after the next 10 elections we still won't have a woman president. That works out to belief in a 16% chance of a woman being elected president any given election. So if anything, the young are less optimistic of women's chances of breaking that ultimate glass ceiling than older voters are.

What CBS (the poll takers) should have done is removed a variable by asking, "Do you think it is likely that a woman will be elected president in the next 30 years?" so that all their respondents were thinking along the same time frame. The woman vs. men answer (an apparent +5% optimism gap toward women) is similarly skewed by age and probably underestimates women's optimism about the topic. Since women live longer, they probably disproportionately make up the (more optimistic) over-65 audience. In fact, we may be able to explain the entire age difference in this response by the gender differences and women's propensity to live longer.

(I realize now that I have used the word "optimism" throughout this article, assuming, dear readers, that like me you'd view the possibility of a female president as a good thing. If for some reason you disagree, please comment with your reasons. Oh and the graphic's title should have been Madam President. No one said that the female president needed to be married.)

06 August 2008

Adjusted ERA+ and leaders

The most important stat for determining the quality a pitcher (at least over the course of a career) is ERA. There's really no argument that can be made against it. Wins are far too dependent on how the rest of the team (i.e., batters) do; strikeouts certainly show domination, and career strikeout numbers show longevity as well. But in terms of doing his job, preventing runs, career ERA is the single number that shows how well this was accomplished. (Okay, perhaps ERA over a 5-10 year peak period might be better, to not diminish the careers of those who choose to keep playing into their "worse than average but far better than replacement" years). The main problems with ERA are that it does not adjust for ballparks, leagues, mound heights, steroid usage of batters, or other factors that changed over the years.

Fortunately, the concept of adjusted ERA takes care of all this. And adjusted ERA+ makes reading adjusted ERA even easier. An aERA+ of 125 roughly means an ERA 25% lower than the league, adjusting for park effects etc. (okay, actually it's 20% lower; since an aERA+ of 200 means an ERA 50% of that of the league). An aERA+ of 125 also means you're likely to need to prepare a speech for Cooperstown. The vast majority of pitchers above 125 and almost all eligible starters above 130 are in the Hall.

For a long time, there was a big gap between aERA+ #1 and #2, with Pedro Martinez's 160 towering over Lefty Grove's 148. This disappointing season has lowered Pedro to a mere 157, but it would take more than a few years of (relative) mediocrity to drop him below Grove or Walter Johnson. (Next on the list, for those curious, is Dan Quisenberry which is probably the best Hall of Fame case for Quisenberry that could ever be made).

But why bring all this up now? Because despite Martinez's struggles, there's now (as of last week) a much bigger gap between #1 and #2:
  1.  Mariano Rivera   197
2. Pedro Martinez 157

What happened? Rivera pitched his 1000th inning last week, making him eligible for career ERA awards. Perhaps the IP requirement should be raised so that the list isn't dominated by closers. But maybe that's not necessary. With the development of the minor-league (or even high school) relief specialist, fewer and fewer will reach this plateau with each passing year. Bochy and Bud Black's cautious use of Trevor Hoffman since he returned from surgery six years ago has kept him from breaking the 1000 IP mark. As of this evening, he's 22 IP short. Since he's been averaging .35 IP per game over the past four seasons, he's likely to end the season (and possibly his career? the fan in me hopes not) about five innings short. His aERA+ of 145 would place him 9th or 10th all time. Prior to this disastrous season, he would have been third.

(For the complete list, see Baseball-Reference.com)

The only other reliever on the ERA title horizon is Billy Wagner, whose aERA+ of 180 would place him comfortably in second, though still at a great distance below Rivera. However with only 819 IP, he will likely need three more seasons to qualify. And anything could happen by then. Troy Percival, with an aERA+ of 151 but only 687 IP, averaging fewer than 50IP/year at age 38, seems highly unlikely to qualify.

Of the other active high saves players, Roberto Hernandez already qualifies at 131; a Hall of Fame number for a starter, but unlikely to get a notice for a reliever with "only" 326 saves. Jose Mesa is what we always thought him to be, exactly an average pitcher (aERA+ of 100), and thus probably a below average reliever. Todd Jones is a little better, but nothing to write to Cooperstown about. Ditto Jason Isringhausen, 100IP short; though kudos to him for having such dreadful recent performances that his manager is bringing back the (great) idea of a closer by committee.

And as far as the brilliant young closers of today. Yes, very exciting. Yes, I know Papelbon has an aERA+ of 269(!); K-Rod at 186; Nathan at 152; Lidge at 147. But for an article on a stat that requires 1000 IP, please ring again when there's just a few hundred innings left to go. Why so pessimistic? Robb Nen, John Wettland, Randy Meyers, Tom Henke, Jeff Montgomery, Rod Beck, Ugueth Urbina, and (ah heck, throw in the newest member of the fraternity) Eric Gagne. If these names mean something to you, you'll understand.