Analyzing the 2025 SC election

(tagging @ArendPeter @larry @ClayShentrup, FYI)

The 2025 Steering Council election is in the books. This is the first using STAR voting, & the BetterVoting.com voting service.

  • 6 candidates
  • 5 winners
  • 106 eligible voters
  • 74 ballots

Verification

Anonymized ballots were downloaded from the election results page, in CSV
format. @larry’s starvote library was used in an attempt to reproduce the voting service’s results. This was successful: results were duplicated in all respects.

Alas, that takes some doing. The CSV format changed in some ways since Larry wrote his library, and there are other ways in which their processing differs. The library “blows up” almost at once with a raw download. Instead it takes some doing to create ballots the library knows how to work with:

Turns out much of this workaround code wasn’t actually needed: there were no ties of any kind along the way, and there were no all-the-same-rank ballots.

[EDIT: oops! I forgot to include the code for manual tiebreaking - repaired]

Python code to drive Larry's library
import starvote
import csv

KEEP_ALL_ThE_SAME = False

METANAMES = {'voterID',
             'voteTime',
             'pollID',
             'ballot_id',
             'precinct'}
FN = "/Users/Tim/Documents/ballots/sc-2025.csv"

def distinct(ballot):
    result = set(ballot.values())
    if len(ballot) < ncands: # not given maps to 0
        result.add(0)
    return result

def manual(options, tie, desired, exception):
    """Resolve tie by hand
In case of a tie requiring randomness, at the prompt
enter a space-separated list of the ordinals of the
required number of winners.
"""
    print(" tied:")
    options.print_candidates(tie, numbered=True)
    xs = input(str(desired) + " wioners: ")
    xs = list(set(map(int, xs.split())))
    assert len(xs) == desired
    return [tie[i-1] for i in xs]

starvote.tiebreakers['manual'] = manual

print("[Building ballots from", FN + ']')
with open(FN, "r", encoding="utf-8-sig") as f:
    it = iter(csv.reader(f))
    cands = next(it)
    ncols = len(cands)
    print('', ncols, "columns:")
    for cand in cands:
        print(' ', cand)
    nmeta = 0
    while cands[nmeta] in METANAMES:
        nmeta += 1
    if nmeta:
        print('', "Removing columns:")
        for i in range(nmeta):
            print(' ', cands[i])
    del cands[:nmeta]
    ncands = len(cands)
    print()

    ballots = []
    for line in it:
        b = {}
        assert len(line) == ncols
        del line[:nmeta]
        for cand, this in zip(cands, line):
            if this:
                b[cand] = int(this)
        if len(distinct(b)) == 1:
            print("ballot all the same:", b)
            if KEEP_ALL_ThE_SAME:
                ballots.append(b)
        else:
            ballots.append(b)

##        starvote.star,
##        starvote.bloc,
##        starvote.allocated,
##        starvote.rrv
##        starvote.sss.
winners = starvote.election(starvote.bloc,
                            ballots,
                            verbosity=1,
                            tiebreaker=manual,
                            seats=5)

Because there were no ties of any kind, Larry’s output is extraordinarily easy to follow:

Output from Larry's starvote
[Building ballots from /Users/Tim/Documents/ballots/sc-2025.csv]
 8 columns:
  ballot_id
  precinct
  Barry Warsaw
  Donghee Na
  Gregory P. Smith
  Pablo Galindo Salgado
  Savannah Ostrowski
  Thomas Wouters
 Removing columns:
  ballot_id
  precinct

[Bloc STAR]
 Tabulating 74 ballots.
 Maximum score is 5.
 Want to fill 5 seats.

[Bloc STAR: Resolve tie by hand]
 In case of a tie requiring randomness, at the prompt
 enter a space-separated list of the ordinals of the
 required number of winners.

[Bloc STAR: Round 1: Scoring Round]
 The two highest-scoring candidates advance to the next round.
   Pablo Galindo Salgado -- 313 (average 4+17/74) -- First place
   Savannah Ostrowski    -- 249 (average 3+27/74) -- Second place
   Barry Warsaw          -- 239 (average 3+17/74)
   Donghee Na            -- 191 (average 2+43/74)
   Thomas Wouters        -- 187 (average 2+39/74)
   Gregory P. Smith      -- 173 (average 2+25/74)
 Pablo Galindo Salgado and Savannah Ostrowski advance.

[Bloc STAR: Round 1: Automatic Runoff Round]
 The candidate preferred in the most head-to-head matchups wins.
   Pablo Galindo Salgado -- 36 -- First place
   Savannah Ostrowski    -- 19
   No Preference         -- 19
 Pablo Galindo Salgado wins.

[Bloc STAR: Round 2: Scoring Round]
 The two highest-scoring candidates advance to the next round.
   Savannah Ostrowski -- 249 (average 3+27/74) -- First place
   Barry Warsaw       -- 239 (average 3+17/74) -- Second place
   Donghee Na         -- 191 (average 2+43/74)
   Thomas Wouters     -- 187 (average 2+39/74)
   Gregory P. Smith   -- 173 (average 2+25/74)
 Savannah Ostrowski and Barry Warsaw advance.

[Bloc STAR: Round 2: Automatic Runoff Round]
 The candidate preferred in the most head-to-head matchups wins.
   Savannah Ostrowski -- 34 -- First place
   Barry Warsaw       -- 29
   No Preference      -- 11
 Savannah Ostrowski wins.

[Bloc STAR: Round 3: Scoring Round]
 The two highest-scoring candidates advance to the next round.
   Barry Warsaw     -- 239 (average 3+17/74) -- First place
   Donghee Na       -- 191 (average 2+43/74) -- Second place
   Thomas Wouters   -- 187 (average 2+39/74)
   Gregory P. Smith -- 173 (average 2+25/74)
 Barry Warsaw and Donghee Na advance.

[Bloc STAR: Round 3: Automatic Runoff Round]
 The candidate preferred in the most head-to-head matchups wins.
   Barry Warsaw  -- 38 -- First place
   Donghee Na    -- 25
   No Preference -- 11
 Barry Warsaw wins.

[Bloc STAR: Round 4: Scoring Round]
 The two highest-scoring candidates advance to the next round.
   Donghee Na       -- 191 (average 2+43/74) -- First place
   Thomas Wouters   -- 187 (average 2+39/74) -- Second place
   Gregory P. Smith -- 173 (average 2+25/74)
 Donghee Na and Thomas Wouters advance.

[Bloc STAR: Round 4: Automatic Runoff Round]
 The candidate preferred in the most head-to-head matchups wins.
   Donghee Na     -- 36 -- First place
   Thomas Wouters -- 33
   No Preference  --  5
 Donghee Na wins.

[Bloc STAR: Round 5: Scoring Round]
 The two highest-scoring candidates advance to the next round.
   Thomas Wouters   -- 187 (average 2+39/74) -- First place
   Gregory P. Smith -- 173 (average 2+25/74) -- Second place
 Thomas Wouters and Gregory P. Smith advance.

[Bloc STAR: Round 5: Automatic Runoff Round]
 The candidate preferred in the most head-to-head matchups wins.
   Thomas Wouters   -- 34 -- First place
   Gregory P. Smith -- 26
   No Preference    -- 14
 Thomas Wouters wins.

[Bloc STAR: Winners]
 Pablo Galindo Salgado
 Savannah Ostrowski
 Barry Warsaw
 Donghee Na
 Thomas Wouters

Ballots by span

By the “span” of a ballot, I mean the difference between the maximum and minimum number of stars a ballot gave. This can vary from 0 through 5 inclusive. A candidate with no stars specified acts the same as if 0 stars were explicitly specified.

The ballot instructions said to give 5 to your favorite, and a 0 to your least favorite, so only ballots with span 5 followed the instructions. In Approval, the span is always 0 or 1.

ballots by span; 74 total
1   3   4.1%  ***
2   5   6.8%  *****
3   8  10.8%  *******
4   9  12.2%  ********
5  49  66.2%  ****************************************

A surprisingly high number of voters followed instructions :wink:.

Ballots by distinct ranks

There are 6 possibilities for the number of stars to give a candidate. My belief coming in was that the runoff phase of STAR would incentivize voters to use that variety to maximize their power in the runoff phase, as opposed to plain score voting (with no runoff phase) where scores are routinely exaggerated to the endpoints to try to gain “strategic” advantage. Oar data says our voters are mostly utilizing most of the possibilities STAR offers.

ballots by number of distinct ranks; 74 total
2  12  16.2%  **********
3   9  12.2%  ********
4  22  29.7%  ******************
5  16  21.6%  *************
6  15  20.3%  *************

There were no all-the-same ballots, but about a sixth of voters restricted themselves to Approval-like only two distinct ranks.

Star distribution

The total number of rankings expressed is the number of ballots times the number of candidates. While “candidate left blank” acts the same as “candidate given 0 stars”, the ballots do record which was the case, and this analysis accounts for them. “Left blank” is indicated by -1.

distribution of stars (-1 = empty); 444 total
-1   21   4.7%  ***
 0   49  11.0%  *******
 1   40   9.0%  ******
 2   45  10.1%  *******
 3   71  16.0%  **********
 4   81  18.2%  ***********
 5  137  30.9%  *******************

I was surprised by that 5 stars is clearly the most popular rank given. It suggests voters are very happy with the candidates, and especially so since 4 stars was the second-most popular choice. The two ways of spelling “0 stars” add up to 70, so far less common.

Proportional representation

Larry’s library also supports 3 variations of “proportional representation” scoring for STAR ballots. All 3 delivered the same winners in the same order as plain Bloc STAR. Not surprising. PR is intended to force minority representation in highly polarized electorates. If our candidates had different positions on anything, it’s hard to tell from their nomination statements :wink:

Other

I think I’m done! In the PSF Board election I made heroic further efforts to try to pin down why multiple versions of proportional Approval did change the final winners (slightly so, and by the tiniest of margins). There’s no similar mystery here to investigate, so I see no reason to go on to do heavy analysis of correlations among candidates and their supporters.

18 Likes

very cool. thanks for sharing.

5 Likes

This points to a human-factors weakness in STAR, or inadequate explanation of how it works: in all those cases, only the highest number of stars were given. Every candidate got 4 or 5 stars in the span=1 ballots, and 3, 4 or 5 in the span=2 cases.

Under any absolute measure, under Approval they probably would have approved of all of them, and it just doesn’t “feel right” to give any of them a rock-bottom 0.

I wrestled with that myself, in fact. A STAR election isn’t asking you to render an eternal absolute judgment of a candidate’s worth, it’s just saying that if you have any preference at all, no matter how slight, in the specific election at hand, the stars you give have to differ else you haven’t expressed that preference.

However, that we only had ~11% of ballots that restricted themselves to span<=2 says most people already figured part of that out. Better to focus on the full third of ballots that didn’t follow the instructions (which only span=5 ballots meet).

2 Likes

Was anyone naïve enough to believe that? :rofl:

Of course I continued digging for my own edification, but wasn’t inclined to share more unless something interesting came up. I think this qualifies.

I developed histograms of star distributions for each candidate, They’re shown here in decreasing order of the number of 5-star ratings each candidate got.

The interesting part: that exactly matches the order in which Bloc STAR picked the winners. In effect, the 5-star ratings turned out to be the only ones that mattered.

More interesting: much the same if you instead order candidates by increasing number of 0-star ratings. Exactly the same order except with a twist: Savannah and Barry tied for the number of 0-star ratings, so it doesn’t wholly define the order in which to pick them. The end result (“who won?”) isn’t affected.

All of which suggests that the data in this election was so clear that just about any way of looking at it would pick the same winners.

Per-candidate star distribution
star distibution for Pablo Galindo Salgado; 74 total
5  43  58.1%  ***********************************
4  17  23.0%  **************
3   9  12.2%  ********
2   1   1.4%  *
1   1   1.4%  *
0   3   4.1%  ***

star distibution for Savannah Ostrowski; 74 total
5  29  39.2%  ************************
4  11  14.9%  *********
3  12  16.2%  **********
2   9  12.2%  ********
1   6   8.1%  *****
0   7   9.5%  ******

star distibution for Barry Warsaw; 74 total
5  26  35.1%  **********************
4   9  12.2%  ********
3  16  21.6%  *************
2   9  12.2%  ********
1   7   9.5%  ******
0   7   9.5%  ******

star distibution for Donghee Na; 74 total
5  15  20.3%  *************
4  14  18.9%  ************
3   9  12.2%  ********
2  12  16.2%  **********
1   9  12.2%  ********
0  15  20.3%  *************

star distibution for Thomas Wouters; 74 total
5  13  17.6%  ***********
4  15  20.3%  *************
3  14  18.9%  ************
2   6   8.1%  *****
1   8  10.8%  *******
0  18  24.3%  ***************

star distibution for Gregory P. Smith; 74 total
5  11  14.9%  *********
4  15  20.3%  *************
3  11  14.9%  *********
2   8  10.8%  *******
1   9  12.2%  ********
0  20  27.0%  *****************
1 Like

Missed one! All my investigations reported here focused on the stars, but preferences also play a key role in STAR. So:

Preferences

If there are C candidates, pref: list[list[int]] is a 2D CxC matrix where pref[i][j] is the number of ballots which give more stars to candidate i than to candidate j, The magnitude of the difference in number of stars doesn’t matter.- just “greater or not?”. I won’t give much analysis here. For our data,

candidate indices
['Barry Warsaw',
 'Donghee Na',
 'Gregory P. Smith',
 'Pablo Galindo Salgado',
 'Savannah Ostrowski',
 'Thomas Wouters']

pref
[[ 0, 38, 40, 10, 29, 39],
 [25,  0, 32, 14, 17, 36],
 [ 8, 32,  0,  8, 22, 26],
 [43, 52, 51,  0, 36, 51],
 [34, 41, 43, 19,  0, 39],
 [19, 33, 34,  9, 22,  0]]

That’s enough input to run a Condorcet score method. If you do, the first three rounds of any iterated Condorcet method will deliver:

round 1 Condorcet winner Pablo Galindo Salgado
round 2 Condorcet winner Savannah Ostrowski
round 3 Condorcet winner Barry Warsaw

and then it’s stuck. There is no Condorcet winner (a single candidate preferred to all others still in play) among the three remaining candidates.

There is no truly compelling way to break such a tie. The fundamental criterion Condorcet methods build on (“a candidate who beats all others head-to-head”) is just plain out the window then. The Schulze method, and the “Ranked Pairs” method, do well in simulations, and Schulze gained most traction. But both rely on detours into graph theory most voters will never understand, and Schulze in particular has been known to make even hardcore tech heads explode :wink:.

The STAR folks wisely (IMO) stuck to simple things anyone can see for themselves in the ballot data - who had the higher score? who is more preferred? who got the most 5-star ratings? And if all those tie, then it resorts to a virtual coin flip.

In the end, our election seems typical to me: there were no ties on any criterion during any step, and we matched Condorcet results as far as pure Condorcet could go. No exploding heads anywhere :smiling_face:.

1 Like

There was some overlap here with the “small span” ballots (those who wouldn’t give out any ratings less than 3 stars), but most of the rest appear to be “bullet ballots”:, typically passing out only 0 or 5 stars. That’s a “strategic” tactic that can pay in pure score voting, but not so much in STAR. It’s the most you can do to get a candidate into (or keep one out of) a runoff phase - but then your ballot likely has no effect in the runoff since it expressed so few preferences.

They may nevertheless be “sincere” ballots. A number appeared to me to voting against a few candidates, giving out 5 stars to most candidates but 0 to just 1 ot 2. Saying “I don’t really care who wins, provided it’s not one of these”.

Then there’s a ballot that gave 5 stars to one candidate - and otherwise left the ballot blank. I’m only surprised by that there’s only one ballot like that :wink:.

1 Like

And, just for fun and curiosity,

The heart poll

Last year, just counting the number of hearts on candidate nomination statements was a strong predictor of the eventual outcomes. Although it has no right to be :wink: It’s a form of self-selected, public, Approval polling. This year apparently even less relevant, since we didn’t even use Approval this time.

Yet it still did far better than random, getting the final ordering right on the nose, except for putting Donghee Na two slots higher than the outcome:

56 Pablo Galindo Salgado
49 Donghee Na
44 Savannah Ostrowski
37 Barry Warsaw
31 Thomas Wouters
27 Gregory P. Smith

Even weirder: the number of hearts a candidate got is roughly equal to the sum of the number of 5- and 4-star ratings they got. Which would make some kind of intuitive sense if you squint hard enough. Except for Mr. Na. He got “a lot” more hearts than 5- and 4-star ratings. Which doesn’t make any kind of intuitive sense to me.

Just another Election Mystery™.

5 Likes

As you know, there are relatively few core developers who are eligible to vote, but because nominee statements are shared within the community, the number of hearts is likely to contain noise.

4 Likes

Ya, but all the candidates are in the same boat that way. There’s no really good reason for why hearts on nomination statements should have any predictive power. Yet they do :wink:

It could be that Mr. Na is unique in having support in the wider community that goes well beyond his visibility among core developers. That would explain a lot.

But. luckily, we don’t really need an explanation here :smiley:.

2 Likes

I think this is in large part due to your explanations on how STAR voting works. For which I thank you!

8 Likes

Aww - you’re very welcome :smiley:.

I know one core dev with no presence on d.p.o., who had no idea we switched voting method or voting service. They were baffled by the email inviting them to vote. They were aware of that an election was coming up, and feared the unfamiliar-looking voting invitation was some kind of surreal phishing attempt.

After multiple reassurance from me and our election admin that the email was legit, I assume they voted. But I don’t assume they followed the instructions.

With people, it’s always something :wink:

1 Like

Verifying that YOUR ballot was counted

While not trivial, it’s possible to verify that the ballot you cast was counted in the results I reproduced in the first message here.

First you have to download the anonymized ballots fron the election result page. I recommend the CSV format because it’s compact, nearly self-evident, and easy to search. The first column is named “ballot_id”. All you need to do is find the row with your ballot_id, and verify that the columns in that row match the number of stars you assigned to each candidate. A column contains a little int in range(6) giving the number of stars you gave to that column’s candidate, or is empty if you didn’t bother to rate that candidate.

A ballot_id is a gibberish string generated by the voting service, from which nothing can be inferred about your identity - not even our election admin can find out your ballot_id. It’s only revealed in computer-generated voting receipt email sent only to you, as part of a URL.

So open a voting receipt email and click on the “verify your ballot and ballot status” link. The address bar will then show something like:

https://bettervoting.com/g8jf8t/ballot/b-a5m8hsuk

Yout ballot_id is the tail end of that:

b-a5m8hsuk

in the example URL.

Happy verifying! You can also use Larry’s library, with the Python driver I posted, to verify the entire election scoring process for yourself.

And I’ll also include a version of the Python code I used to produce the various analyses.

Python analysis code
from collections import defaultdict
from math import ceil
from operator import itemgetter
get0 = itemgetter(0)
get1 = itemgetter(1)

def hist(tag, pairs, *, ljust=False, nstars=60):
    keys, values = zip(*pairs, strict=True)
    keys = list(map(str, keys))
    kwidth = max(map(len, keys))
    vwidth = len(str(max(values)))
    total = sum(values)
    print(tag, "; ",
          format(total, '_'), " total",
          sep='')

    for k, v in zip(keys, values):
        p = v / total
        print((k.ljust if ljust else k.rjust)(kwidth), "  ",
               str(v).rjust(vwidth), "  ",
               format(p, "5.1%"), "  ",
               "*" * ceil(p * nstars),
               sep="")

d = defaultdict(int)

d.clear()
for b in ballots:
    small = large = 0
    if b:
        small = min(b.values())
        large = max(b.values())
    if len(b) < ncands:
        small = 0
    d[large - small] += 1
##    if (diff := large - small) <= 2:
##        print("diff", diff, b)
##        input("MORE")
items = sorted(d.items(), key=get0)
hist("ballots by span", items)

d.clear()
for b in ballots:
    d[len(distinct(b))] += 1
##    if (dd := len(distinct(b))) <= 2:
##        print(dd, b)
##        input("MORE")
items = sorted(d.items(), key=get0)
hist("ballots by number of distinct ranks", items)

# The total number of ranks given is the number of cnadidates
# times the number of ballots.
d.clear()
for b in ballots:
    if len(b) < ncands:
        d[-1] += ncands - len(b)
    for a in b.values():
        d[a] += 1
items = sorted(d.items(), key=get0)
hist("distribution of stars (-1 = empty)", items)
assert sum(map(get1, items)) == ncands * len(ballots)

d.clear()
for b in ballots:
    dd = distinct(b)
    if len(dd) == 1:
        d[dd.pop()] += 1
items = sorted(d.items(), key=get0)
if items:
    hist("distribution of all-the-same", items)

# Candidate scores
scands = sorted(cands)
L = []
for c1 in scands:
    d.clear()
    for b in ballots:
        d[b.get(c1, 0)] += 1
    L.append((c1, sorted(d.items(), key=get0, reverse=True)))
L.sort(key=get1, reverse=True)
for c1, items in L:
    hist("star distibution for " + c1, items)
del L

pref = [[0] * ncands for i in range(ncands)]
for b in ballots:
    for i, c1 in enumerate(scands):
        row = pref[i]
        for j, c2 in enumerate(scands):
            if b.get(c1, 0) > b.get(c2, 0):
                row[j] += 1

from pprint import pprint
print("candidate indices")
pprint(scands)
print("pref")
pprint(pref)
inplay = set(range(len(pref)))
for round in range(1, len(inplay)):
    for i in inplay:
        winner = -1
        if all(pref[i][j] > pref[j][i] for j in inplay - {i}):
            winner = i
            break
    if winner >= 0:
        print("round", round, "Condorcet winner", i, scands[winner])
        inplay.remove(winner)
    else:
        print("ERROR: no Condorcet winner in", inplay)
        for i in inplay:
            print(*(f"{pref[i][j]:2}" for j in inplay))
        break
1 Like

You reported a few analyses that mostly show that various alternative ways of processing the results (e.g., looking at just 5 stars, just 0 stars, or hearts) all give basically the same results as the actual election. Doesn’t it seem likely that a big reason for this is simply that there were only 6 candidates for 5 seats?

Given this, there are only 6 possible outcomes in terms of who is elected (ignoring ordering). Even considering ordering, the number of possibilities is relatively small (720) and many of them are implausible except under pathological conditions (e.g., if the final ranking were in reverse order of the number of 5-star votes received). This is in contrast to the PSF election which had 13 candidates for 4 seats, so 715 possible outcomes and 17000+ possible orderings.

Not to say that these analyses aren’t interesting in an academic sense, but personally I’m not super surprised that many aggregation methods give the same result in an election in which so few distinct results are possible.

2 Likes

Gini coefficient

A chatbot (ChatGPT-5) suggested this one! I was looking for a measure of “how uniform” support for a candidate was. We discussed and dismissed many possibilities.

For example, how about looking at the mean number of stars a candidate got, and using the standard deviation of that distribution as a measure (the lower the sdev, the tighter the distribution)? That could work well if ratings actually reflected voters’ absolute utility judgments, but they don’t. STAR encourages spanning the whole range of possible scores, and inflating even the tiniest preference ratings to reach different integer scores.

And so on. Then the chatbot suggested looking at Gini coefficients. If it wasn’t a bot, I would have said “brilliant!” :wink:.

You’ve heard of those, but in the context of income inequality, usually applied to entire countries. The more uniformly distributed income is, the closer the Gini coefficient gets to 0.0, and the more skewed it is, the closer to 1.0.

In our context, 0.0 is also the “good extreme”: the less skewed the number of stars a candidate got, the more uniform their support. The mean number of stars they got is irrelevant.. I’m trying to get a handle on how consistently voters viewed a candidate, not on how much they’re liked or disliked. Gini appears to capture just that!

Kind of :wink::. All ways of reducing realities to “a number” have to be viewed with extreme skepticism. They can nevertheless be suggestive.

For our candidates, least to most “divisive”:

Pablo Galindo Salgado     0.130
Savannah Ostrowski        0.275
Barry Warsaw              0.288
Donghee Na                0.399
Thomas Wouters            0.411
Gregory P. Smith          0.445

And probably far from coincidental that this is - once again - the same order in which Bloc STAR picked them.

[EDIT: added code to compute this]

Python code to compute star inequality
from fractions import Fraction
n = len(ballots)
pairs = []
for c in scands:
    L = [b.get(c, 0) for b in ballots]
    L.sort()
    gini = Fraction(2 * sum(i * v for i, v in enumerate(L, start=1)),
                    n * sum(L))
    gini -= Fraction(n + 1,  n)
    pairs.append((c, float(gini)))
pairs.sort(key=get1)
for c, g in pairs:
    print(f"{c:25} {g:.3f}")
2 Likes

And I just posted another: using Gini coefficients to rank candidates by “star inequality”. I love that one :wink:.

Oh yes! I noted before that “pick 5 winners out of 6” is quite close to a degenerate case.

This election isn’t really technically interesting from an “election science” view, except maybe for its demonstrating that, given the chance, people mostly will express nuance beyond just “approve or don’t”.

For me it was mostly an excuse to write code, and share it, for different ways of analyzing STAR election results. I won’t always be around, but I do know some hard-won things others mostly don’t, and I hope the real value here will be rediscovered after I’m gone.

In the meantime, it’s reassuring that these different ways to slice and dice aren’t giving materially different outcomes in such a technically simple election.

Not “super” here either, but “somewhat surprised”, yes. Getting the same one of 720 different permutations is really quite unlikely on its own - but certainly not “astronomically” unlikely.

The bottom 3 star-getters had close enough total scores that I’m more surprised not to see more variation among their ordering. But then pure Condorcet methods gave up when reaching them (intransitive group preference ordering), so no guessing how those would have ended without picking one of the many ad hoc ways invented to bail out pure Condorcet then.

But even the frivolous 'heart poll" method got those right too.

And, ya, that is somewhat surprising to me, even reduced to “1 of 6 possible permutations”.

2 Likes

Although that wouldn’t hold in general. In the PSF Board election, one candidate was very widely disliked. But so very widely and deeply disliked that they certainly would have gotten a very low Gini coefficient too: the number of stars they got would vary very little across ballots.

So this is a potentially very interesting measure that just so happens to say very little in this election, because our “very uniform” candidates also happened to be the most widely endorsed.

That’s not a function of the number of open seats or of the number of winners, but of how the candidates themselves are perceived by the electorate.

Which is the heart of why this election is so “technically boring”: all the candidates were technically qualified for the job, and the ballots reflected that there simply wasn’t enough deep disagreement across voters to drive “interesting” things to discover.

While I’m here, I’ll point something out that may have been lost: if you look at the distribution of stars by candidate, you’ll see that two candidates got more 0-star ratings than 5-star. So if you’d like to run for the SC but think it’s hopeless, think again: that on its own says some of these candidates were vulnerable. Indeed, the one fresh face who ran was picked second.

STAR reveals a world of info that can otherwise only be guessed at. Take advantage of it :smile:.

2 Likes

And the Gini coefficient is no exception. There is no “dirt simple” way to explain exactly what it measures. Possibilities at extremes:

  • If everyone gives a candidate the same number of stars, it’s 0.0 (perfect “income equality”),.

  • But if a candidate gets a single 5-star rating, and all the rest 0, it’s close to 1.0. Extreme “income inequality”.

  • And if there’s a balanced mix of high and low ratings, it’s close to 0.5.

Best advice: don’t try to read too much into it. It’s just a number, after all :wink: .

And I retract my earlier claim that the very unpopular Board candidate would certainly get a low Gini coefficient. It’s the opposite: because of the second case above, they’d get a high one. Just a few 5s in an ocean of 0s, an extremely unequal distribution of stars.

1 Like

I’m worried that all of this analysis may actually have the opposite effect.

If I ran for SC (itself a very public, vulnerable act) and lost… whatever, I’d be fine. But it sounds like my personal hell to then have the numbers behind my loss publicly sliced and diced a dozen different ways seemingly to remind everybody of the mathematical fact that I *deserved* to lose (and just how bad it was).

While sort of interesting in the context of the switch to STAR this year, I hope threads like this don’t become tradition. At the end of the day, it’s literally ranking people, and I worry that the few (if any) benefits of this exercise won’t outweigh the harm to the size and makeup of future candidate pools, which are already suffering.

20 Likes

A hope I join you in :slight_smile: It’s the switch to a new election system that drove this: it’s brand-new territory to many, and I think I know non-trivial things about it (it’s not new territory to me) that people are unlikely to find out otherwise.

You now have a guide to questions “worth answering” in the future, and code you can adapt to answer them.

When I analyzed the Board election, I left names out of it entirely. There was some controversy in that one, and some hard feelings before I stepped in. The names added no value to the analysis.

In this case, while I may well be mistaken, and my apologies if so, I felt this was “all among friends”, there was no controversy apparent to me, and no hard feelings on display. I’m truly sorry if I triggered some. I can only say that as “a Golden Rule” guy, I know I’d be fine with having my election results analyzed to the n’th degree if someone was so inclined. But, ya, that’s just me.

Anyway, barring any questions that may come up, I’m done here.

In future STAR elections, in the absence of controversies demanding “an explanation”, if I’m around I’ll say very much less, akin to just the very first post in this topic. No names at all.

2 Likes

Well, personally, I would certainly like @gpshead to run for the SC again next year, as from the surface it seems to me that he has been doing quite a good job.

10 Likes