What about using the labels as the point markers, rather than having separate labels? Here's an example using ggplot2
rather than base graphics.
In order to avoid overlaps, we directly set the amount of vertical offset for repeated values, rather than leaving it to random jitter. To do that, we need to assign numerical y-values (so that we can add the offset) and then replace the numerical axis labels with the appropriate text labels.
library(ggplot2)
library(reshape2)
library(dplyr)
# Convert data from "wide" to "long" format
CTscores.m = melt(CTscores, id.var="initials")
# Create an offset that we'll use for vertically separating the repeated values
CTscores.m = CTscores.m %>% group_by(variable, value) %>%
mutate(repeats = ifelse(n()>1, 1,0),
offset = ifelse(repeats==0, 0, seq(-n()/25, n()/25, length.out=n())))
ggplot(CTscores.m, aes(label=initials, x=value, y=as.numeric(variable) + offset,
color=initials)) +
geom_text() +
scale_y_continuous(labels=sort(unique(CTscores.m$variable))) +
theme_bw(base_size=15) +
labs(y="", x="") +
guides(color=FALSE)

For completeness, here's how to create the graph with jitter for the repeated values, rather than with a specific offset:
# Convert data from "wide" to "long" format
CTscores.m = melt(CTscores, id.var="initials")
# Mark repeated values (so we can selectively jitter them later)
CTscores.m = CTscores.m %>% group_by(variable, value) %>%
mutate(repeats = ifelse(n()>1, 1,0))
# Jitter only the points with repeated values
set.seed(13)
ggplot() +
geom_text(data=CTscores.m[CTscores.m$repeats==1,],
aes(label=initials, x=value, y=variable, color=initials),
position=position_jitter(height=0.25, width=0)) +
geom_text(data=CTscores.m[CTscores.m$repeats==0,],
aes(label=initials, x=value, y=variable, color=initials)) +
theme_bw(base_size=15) +
guides(color=FALSE)
