Using AppleScript to mail merge with attachments

Here is some code that is useful for sending batch emails with Mac Mail to recipients using information stored in a text file. This also enables attaching specific files to the message, again based on the information in the text file.

set mol_list to {}
-- This will ask you to select a file containing the intended recepients and their emails --
-- I also include in this file information needed to link to an attachment --
set theFile to choose file with prompt "Select a text file:"
set theFileReference to open for access theFile
-- Note that the line end here is an old Mac return (not MSFT carriage return) --
set theFileContents to read theFileReference using delimiter return
close access theFileReference

-- Now parse the file that was selected. Here I'm parsing a tab-delimited file. --
set text item delimiters to tab
-- Loop through the file one line at a time --
repeat with i from 1 to count of theFileContents
	set theLine to text items of item i of theFileContents
	copy theLine to the end of mol_list
	-- this identifies each column in the tab-delimited file --
	set stid to item 1 of theLine
	set first_name to item 2 of theLine
	set last_name to item 3 of theLine
	set email_add to item 4 of theLine
	-- specify the location of the file to attach --
	-- here I'm pasting together information from the tab-delimited file to point to the file for this particular recipient --
	set file_attach to "Macintosh HD:Users:USERID:file_" & stid & ".pdf"
	-- Set the message, again pasting together info from the recipient file --
	set message_content to "Dear " & first_name & ",
	This is my email to you containing your information. 
	-- Now push this to Mac's mail software
	tell application "Mail"
		-- Create a new message with the message above and the subject --
		set theMessage to make new outgoing message with properties {visible:true, subject:"Set the subject here", content:message_content}
		-- Set the address for this recipient --
		tell theMessage
			make new to recipient at end of to recipients with properties {address:email_add}
		end tell
		-- Add the attachment to this recipient --
		tell content of theMessage
			make new attachment with properties {file name:file_attach as alias} at after last paragraph
		end tell
		-- Add a little delay to attach larger files to the email before sending --
		delay 2
		-- Send the message to this recipient --
		send theMessage
	end tell
end repeat

Writing a new method for nlme

I recently took some time to figure out how to write a new method for nlme to enable structuring the variance-covariance matrix of the random effects in a specific way. My goal here was to be able to run Dave Kenny’s social relations model (Kenny, 1994) using multilevel modeling and the approach described by Snijders and Kenny (1999). Taking this approach requires “tricking” the software in a way through the use of dummy variables and constraints on the variance-covariance matrix.

Figuring out how to write a new method was more challenging than I had initially expected. There are many twists and turns in lme and it took quite a bit of time to reverse engineer the software to figure out what was going on. Unfortunately, there isn’t great documentation on the web for this process.

As part of my process, I created my own replication of one of the existing methods–pdCompSymm. I went through and commented each part of the different functions that are called, explaining my interpretation of what is going on. As you can see, there are some places where I’m just off and don’t really know what’s going on. I also converted some of the C code in nlme for running pdCompSymm into R code (this is the pdFactor.pdCompSymm function).

In the end, I was able to figure out enough of it to succeed in my goal of creating a new method for the social relations model through multilevel modeling in R. You can find this on my github page. I‘ve called it pdSRM and it has some comments at the top that explain how to use it.

One lesson learned from this is that it is challenging–but not impossible!–to specify a structure for the variance-covariance matrix using nlme that is not already in the generic methods that are provided. I also learned a ton about how lme is working behind the scenes. This took a bunch of time, but did pay off in the end.

Trying out Sublime Text 3

I’ve been a huge fan of TextWrangler for years. I use it for all of my coding (including with R), for taking notes during meetings, for taking notes on articles, and more. It’s my most frequently used application. But, I’m ready for a change and am going to give Sublime Text 3 a try. It seems very powerful, relatively light, and incredibly extensible. 

I recently installed Sublime Text 3, added Package Control, and installed some R packages to try out. I’ll give it a try for the next three weeks and see what I think. It might be time to say farewell to TextWrangler.

Recurrence Quantification Analysis

This page has some wonderful resources for recurrence analysis. One particularly useful resource on this site is the listing of software options for conducting recurrence analysis. After a fair amount of searching, I couldn’t find an R package that computed the metrics from a recurrence quantification analysis. The tseriesChaos package provides a function for producing recurrence plots; but, I didn’t see anything for quantifying these plots.

After digging through the different software options listed on this site, I tried out and really like the Commandline Recurrence Plots script offered by Norbert Marwan himself.

The script was very easy to setup on my Mac and, by using Rscript it was easy to combine with R code to (a) draw specific chunks of data for different individuals in my dataset; (b) compute and output the recurrence quantification metrics; (c) output the recurrence plot dataset for creating the actual plot; and, (d) producing the plot and creating a dataset of metrics.

I’ll clean up, comment, and post the code that I used as soon as I can come up for air.

R Graphics Parameters — Rows and Columns

For some reason I always forget the code for setting R’s graphics parameters. And, I always need this same line. So, now I shan’t forget it.

par(mfrow=c(3,2), cex=1, mar=c(2,2,2,2))

Convert CD tracks to mp3 using ffmpeg

Just a small chunk of code to convert CD tracks (aiff) to mp3 files:

for i in {1..12}
ffmpeg -i ${i}.aiff -f mp3 -acodec libmp3lame -ab 192000 -ar 44100 ${i}.mp3

Ruby code to parse and combine text files

I use this ruby code to parse several tab-delimitted text files that contain individual raters’ perceptions of a target (in this case a video). The rater id is embedded in the filename. The target video number is also embedded in the filename.

#! /usr/bin/env ruby

out = Dir.glob('*.txt')

# open the file to write to and add the column headers
columns = "grouptratertmintengagetpreparetdivergetconvergetexecutetcentralizetattentivettonetactivationn""./all_ratings.txt", 'w') { |f| f.write(columns) }

out.each do |filename|
rater = filename.split('.')[0].split('_')[0]
group = filename.split('.')[0].split('_')[1]

# Assign a number for the rater
case rater.downcase
when "rater1"
rater_id = 1
when "rater2"
rater_id = 2
when "rater3"
rater_id = 3
when "rater4"
rater_id = 4
puts "rater: " + rater + "(#{rater_id})" + " group: " + group

# Open the file
f =, "r").read

# Split by lines - This will make sure that the end of line from Mac Classic is n
str = f.gsub!(/rn?/, "n").split("n")

# Identify the line number that starts the data entry for this file by finding a specific expression in the text of the file

linenum = 0
exp = "- Low marked by sluggishness"
line = str[linenum]
puts line
until line.include?(exp)
line = str[linenum]
linenum += 1

linenum.upto(linenum+30) do |currentline|
min = (currentline-linenum)+1
# add the rater_id and the group_id to the line
line = group.to_s + "t" + rater_id.to_s + "t" + str[currentline] + "n""./all_ratings.txt", 'a') { |f| f.write(line) }

Copy files from incrementally-numbered drives

This code moves through drives (attached via USB) that are numbered incrementally and copies the files on the drives to the local hard disk. I’m using this to more quickly pull the data off of a number of Affectiva Q-Sensors, which I connect to my computer with a USB hub.

for i in {1..20}
# Create the directory
mkdir "./sensor_data/${i}"
# Check to see if the volume is mounted
if mount|grep $drive;
echo "${drive} is mounted"
# move the files over to the directory
cp -r /Volumes/${drive}/ ./sensor_data/${i}/
echo "${drive} is NOT mounted"

Create a filled line graph with R

I used the code below to create a presentation-quality graph of data on how individual’s activation level (measured as electrodermal activity) changes over time during a group-based task. (Click on the image to enlarge.)

par(family="Tahoma", bg="white", mar=c(3,3,3,3), mgp=c(1,1,1))

ylim <- c(-1.5, 1.5)
xlim <- c(-540, 2700)
x <- aggsub$task_time
y <- aggsub$eda_z
ylo <- rep(min(y), length(y))
plot(x,y, type="n", ylim=ylim, axes=FALSE, ylab="Electrodermal Activity", xlab="Time (in minutes)", xlim=xlim, col="dark green")

xpos <- seq(-540, 2700, 180)
lab <- seq(-9, 45, 3)
axis(1, at=xpos, labels=lab, cex =1.5, lwd=.5, lty=3, tck=1, col="dark gray", pos=-1.5, col.axis="dark gray")

ypos <- seq(-1.5, 1.5, .5)
axis(2, at=ypos , labels=ypos, cex =1.5, las=2, tck=1, lwd=.5, lty=3, col="dark gray", pos=-540, col.axis="dark gray")

zerox <- -540:2700
zeroy <- rep(0, length(zerox))
lines(zerox, zeroy, lty=1, lwd=2, col="red")

lines(x,y, lwd=2.5,col="dark green")
xx <- c(x, rev(x))
yy <- c(ylo, rev(y))
polygon(xx, yy, col="light green", border=FALSE)

sessionstart <- min(x)
taskstart <- 0
taskend <- 1800
recordend <- 1920
sessionend <- max(x)

polygon(c(sessionstart, sessionstart, taskstart, taskstart), c(1.5, min(y), min(y), 1.5), col="#0015FF25", border=FALSE)
text(-270, 1.25, "Pre-Task Survey", col="dark blue")

#polygon(c(taskstart, taskstart, taskend, taskend), c(1.5, min(y), min(y), 1.5), col="#EAFF0025", border=FALSE)
text(900, 1.25, "Group Members Work to Develop Recruitment Video", col="dark green")

polygon(c(taskend, taskend, recordend, recordend), c(1.5, min(y), min(y), 1.5), col="#FA050535", border=FALSE)
text(recordend-(recordend-taskend)/2, 1.25, "RecordnVideo", col="red")

polygon(c(recordend, recordend, sessionend, sessionend), c(1.5, min(y), min(y), 1.5), col="#0015FF25", border=FALSE)
text(sessionend-(sessionend-recordend)/2, 1.25, "Post-Task Survey", col="dark blue")