Have an idea?

Visit Sawtooth Software Feedback to share your ideas on how we can improve our products.

Least fill based on attribute ratings.

I have a similar question. My survey has 10 attributes on a 5 point satisfaction grid with a DK option. Respondents will be asked one or two follow up open-end questions if the ratings have a value of 4 or 5. If only one of the attributes is rated a 4 or 5, one follow up open is asked. If more than one attribute is rated a 4 or 5, two follow up questions are asked.

I have created a constructed list that gathers all of the attributes with a value of 4 or 5 and randomized the list. If more than two attributes have a value of 4 or 5, I need to choose the least filled attributes with a value of 4 or 5 for the follow up opens to try for as even a distribution as possible.

I am working in version 8.3.6. Any help will be greatly appreciated.
related to an answer for: Least Fill List
asked Jul 22 by jrasmussen720 (255 points)

1 Answer

+1 vote
 
Best answer
I'm glad you found that previous post, but that solution has some weaker performance and less flexibility than similar code I have written since then.  The latest version of this code is the "Multi-Select Quota" within the Community Question Library:

https://sawtoothsoftware.com/resources/question-library/multi-select-quota

SSI Web doesn't have the ability to import questions from the Community Question Library, but we should be able to recreate it in SSI Web for you.

Start by adding two numeric pass-in fields to your study, with names like "MultiSelectQuotaHelper1" and "MultiSelectQuotaHelper2".

Next we should add two quota questions to our survey, split by a page break.  They should be named something like "MultiSelectQuota1" and "MultiSelectQuota2".  Both quota questions should have ten quota cells each, all with a cell limit like 99999.  The question that respondents skip to if they don't qualify can be set to whatever - it shouldn't actually happen with our limits so large.

The first quota cell of the first quota question should be given this qualification logic:

Begin Unverified Perl
# ****************************************************************************************************
# Parameters
# ****************************************************************************************************
my $quotaBaseName = 'MultiSelectQuota';
my $passInBaseName = 'MultiSelectQuotaHelper';

my $numberOfQuotas = 2; # the number of quota questions, and the maximum number of choices to make for any respondent
my $numberOfQuotaCells = 10; # the number of cells per quota question, and the number of options to choose from
my $numberOfChoices = 2; # the number of choices to make for this respondent, between 0 and $numberOfQuotas

my $membershipOrder = 2; # 1 - sequentially, 2 - least fill, 3 - randomize

my %qualifications = ( # whether the current respondent should qualify for a given cell; by default, all cells are qualified
    
);

# ****************************************************************************************************
# Run
# ****************************************************************************************************

my @options = ();

# Membership is sequential
if ($membershipOrder == 1) {
    @options = (1 .. $numberOfQuotaCells);
}

# Membership is least fill
elsif ($membershipOrder == 2) {    
    # Calculate the score of each cell
    my $leastFill;
    for (my $cell = 1; $cell <= $numberOfQuotaCells; $cell++) {
        my $completes = 0;
        my $limit = 0;
        for (my $quota = 1; $quota <= $numberOfQuotas; $quota++) {
            $completes += QUOTACELLCOMPLETES($quotaBaseName . $quota, $cell);
            $limit += QUOTACELLLIMIT($quotaBaseName . $quota, $cell);
        }
        my $score = $completes / $limit;
        
        if (exists $leastFill->{$score}) {
            push(@{$leastFill->{$score}}, $cell);
        }
        else {
            $leastFill->{$score} = [$cell];
        }
    }
    
    # Record cells ordered by score, randomizing on ties
    foreach my $score (sort {$a <=> $b} (keys(%{$leastFill}))) {
        my @cells = @{$leastFill->{$score}};
        for (my $i = scalar(@cells); --$i; ) {
            my $j = int(rand($i + 1));
            next if $i == $j;
            @cells[$i, $j] = @cells[$j, $i];
        }
        foreach my $cell (@cells) {
            push (@options, $cell);
        }
    };
}

# Membership is randomize
else {
    @options = (1 .. $numberOfQuotaCells);
    for (my $i = $numberOfQuotaCells; --$i; ) {
        my $j = int(rand($i + 1));
        next if $i == $j;
        @options[$i, $j] = @options[$j, $i];
    }
}

# Check qualification logic and save to pass-in fields
my $nextChoice = 1;
for (my $i = 0; $i < $numberOfQuotaCells && $nextChoice <= $numberOfChoices; $i++) {
    my $cell = $options[$i];
    if (!(exists $qualifications{$cell}) || $qualifications{$cell}->()) {
        SETVALUE($passInBaseName . $nextChoice++, $cell);
    }
}

# Check this cell
return GETVALUE($passInBaseName . 1) == 1;
End Unverified


Lines 5-6 should be updated with the "base name" that you gave the quota questions and pass-in fields.

Lines 14-16 should be updated with the logic that determines whether respondents should qualify for each attribute.  If I'm understanding your grid question correctly, it should look like this:

my %qualifications = ( # whether the current respondent should qualify for a given cell; by default, all cells are qualified
    1 => sub { my $value = GETVALUE('GridQ_r1'); return $value == 4 || $value == 5; },
    2 => sub { my $value = GETVALUE('GridQ_r2'); return $value == 4 || $value == 5; },
    3 => sub { my $value = GETVALUE('GridQ_r3'); return $value == 4 || $value == 5; },
    4 => sub { my $value = GETVALUE('GridQ_r4'); return $value == 4 || $value == 5; },
    5 => sub { my $value = GETVALUE('GridQ_r5'); return $value == 4 || $value == 5; },
    6 => sub { my $value = GETVALUE('GridQ_r6'); return $value == 4 || $value == 5; },
    7 => sub { my $value = GETVALUE('GridQ_r7'); return $value == 4 || $value == 5; },
    8 => sub { my $value = GETVALUE('GridQ_r8'); return $value == 4 || $value == 5; },
    9 => sub { my $value = GETVALUE('GridQ_r9'); return $value == 4 || $value == 5; },
    10 => sub { my $value = GETVALUE('GridQ_r10'); return $value == 4 || $value == 5; }
);


("GridQ" being the name of your grid question.)

All other quota cells' qualification logic should just be "[PASS-IN HELPER BASE NAME][QUOTA NUMBER] = [QUOTA CELL NUMBER]".  So the second cell of the first quota question will look like "MultiSelectQuotaHelper1 = 2" and the third cell of the second quota question will look like "MultiSelectQuotaHelper2 = 3".

Does that all make sense?  I know it's a lot.
answered Jul 22 by Zachary Platinum Sawtooth Software, Inc. (181,275 points)
selected Jul 23 by jrasmussen720
...