Modify Cell Content - Remove Decimals Without Rounding

From Q
Jump to navigation Jump to search

This rule modified the selected statistics to remove the decimal places after a certain number without rounding the numbers. Unlike various rounding procedures this does not change the last decimal place.

Example

In this example, the Average has been selected as the statistic to modify, and the number of decimal places chosen is 1. All decimal places after the first now show zeros. Note that unlike other rounding procedures, this does not change the decimal place that has been kept (that is, the first decimal place).

TruncatingValues.png

Technical details

Significance tests are conducted on the results prior to the modification.

You can choose to modify all numeric statistics shown on the table, or you can select a set of statistics to modify.

You can also choose whether or not marginal statistics (Statistics - Below and Statistics - Right) are included in the modification.

A foot note is added to explain which statistics have been modified in this way.

How to apply this rule

For the first time in a project

  • Select the table(s)/chart(s) that you wish to apply the rule to.
  • Start typing the name of the Rule into the Search features and data box in the top right of the Q window.
  • Click on the Rule when it appears in the QScripts and Rules section of the search results.

OR

  • Select Automate > Browse Online Library.
  • Choose this rule from the list.

Additional applications of the rule

  • Select a table or chart that has the rule and any table(s)/chart(s) that you wish to apply the rule to.
  • Click on the Rules tab (bottom-left of the table/chart).
  • Select the rule that you wish to apply.
  • Click on the Apply drop-down and choose your desired option.
  • Check New items to have it automatically applied to new items that you create. Use Edit > Project Options > Save as Template to create a new project template that automatically uses this rule.

Removing the rule

  • Select the table(s)/chart(s) that you wish to remove the rule from.
  • Press the Rules tab (bottom-right corner).
  • Press Apply next to the rule you wish to remove and choose the appropriate option.

How to modify the rule

  • Click on the Rules tab (bottom-left of the table/chart).
  • Select the rule that you wish to modify.
  • Click Edit Rule and make the desired changes. Alternatively, you can use the JavaScript below to make your own rule (see Customizing Rules).

JavaScript

includeWeb('Table JavaScript Utility Functions'); 

form.setHeading("Remove Decimals Without Rounding");
let description = form.newLabel("Truncate the cell values to a fixed number of decimal places");
description.lineBreakAfter = true;
 
// Decimal number control
let decimals_label = form.newLabel("Decimal places to keep: ");
let decimals_box = form.newNumericUpDown("nd");
decimals_box.setDefault(1);
decimals_box.setIncrement(1);
decimals_box.setMinimum(0);
decimals_box.setMaximum(10);
decimals_box.lineBreakAfter = true;
 
// Apply to marginal statistics?
let marginals_check_box = form.newCheckBox("mcb", "Apply to marginal statistics");
marginals_check_box.lineBreakAfter = true;
marginals_check_box.setDefault(true);
 
// Apply to all statistics?
let all_stats_box = form.newCheckBox("ascb", "Apply to all statistics");
all_stats_box.lineBreakAfter = true;

 
let controls = [description, decimals_label, decimals_box, marginals_check_box, all_stats_box];
form.setInputControls(controls);
 
let all_stats = all_stats_box.getValue(); 
let num_decimals = decimals_box.getValue();
let do_marginals = marginals_check_box.getValue();

// List of statistics for the menu. Text-based stats, or stats which are
// already integers (eg Base n) are not included.
let not_selected_string = "(none)";
let stat_list = [not_selected_string].concat(table.availableStatistics);

// Do not offer to modify these statistics as are text or alredy integers
let stats_to_remove =  ["Base n", "Column n", 
                        "Expected n", "Missing n", "n",
                        "n Observations", "Not duplicate", "Row n", "Text",
                        "Text With No Blanks", "Unique Text"];

stat_list = stat_list.filter(function (s) { 
    return stats_to_remove.indexOf(s) == -1 && !isTextStatistic(s); 
});
 

let all_selected_stats = table.statistics;
if (do_marginals) {
    if (rightTableExists())
        all_selected_stats = all_selected_stats.concat(right_table.statistics);
    if (belowTableExists())
        all_selected_stats = all_selected_stats.concat(below_table.statistics);
}
 
// If not applying to all stats then get the user to select which stats to use
let stats = [];
let translated_stats;
if (!all_stats) {
    let stat_list_translated = translateStats(stat_list);
    let stat_select_label = form.newLabel("Apply only to:");
    stat_select_label.lineBreakAfter = true;
    controls.push(stat_select_label);
    let counter = 0;
    let new_stat_box, last_selection;

    let make_new_stat_box = function () {
        new_stat_box = form.newComboBox("sb" + counter, stat_list_translated);
        new_stat_box.setDefault(not_selected_string);
        new_stat_box.lineBreakAfter = true;
        controls.push(new_stat_box);
        form.setInputControls(controls);
        last_selection = new_stat_box.getValue();
    };

    make_new_stat_box();

    while (last_selection != not_selected_string) {
        stats.push(last_selection);
        counter ++;
        make_new_stat_box();
    }
    translated_stats = stats;
    stats = untranslateStats(stats,stat_list_translated,stat_list);
} else {
    stats = all_selected_stats;
}
 
 
// Create messages to use in summary and footer 
let decimals_string = num_decimals + " decimal place" + (num_decimals == 1 ? "" : "s");
let summary_text = "Remove decimals (without rounding) after " + decimals_string + ": " 
                    + (all_stats ? "all statistics" : translated_stats.slice(0, Math.min(3, stats.length)).join(", ") + (stats.length > 3 ? "..." : ""));
form.setSummary(summary_text);

 
// Change table stats

truncateTable(table, function (stat, truncated_stats) {
    table.set(stat, truncated_stats);
});

if (do_marginals) {
    if (rightTableExists()) {
        truncateTable(right_table, function (stat, truncated_stats) {
            setMarginalStatistic(right_table, stat, truncated_stats);
        });
    }
    if (belowTableExists()) {
        truncateTable(below_table, function (stat, truncated_stats) {
            setMarginalStatistic(below_table, stat, truncated_stats);
        });
    }
}

// Set footer
if (!isRTable()) {
    let trimmed_stats = stats.filter(function (s) { return all_selected_stats.indexOf(s) > -1 });
    if (all_stats || trimmed_stats.length > 0) {
        let footer = table.extraFooters;
        let footer_message = (all_stats ? "All statistics" : translateStats(trimmed_stats).join(", ")) 
                            + " have decimals removed after " + decimals_string;
        footer.push(footer_message);
        table.extraFooters = footer;
    }
}

// For each selected statistic, get its values, truncate them, and then
// fire a function that decides how to apply them to the table.
//
// apply_truncated_stats should be a function(stat, 2d_stats_array) {}
function truncateTable(ttable, apply_truncated_stats) {
    ttable.statistics.forEach(function (stat) {
        if (stats.indexOf(stat) > -1) {
            let current_values = ttable.get(stat);
            if (typeof current_values[0][0] != "string") {
                apply_truncated_stats(stat, truncateStats(current_values, num_decimals));
            }
        }
    });    
}

// Truncate the number to a certain number of decimal places
// by converting the number to a string, cutting the number
// of decimals, then converting back to a float.
function truncateDecimalNumber(x, num_decimals) {
    if (isNaN(x) || x == 0)
        return x;
    
    let num_string = x.toPrecision(13); // Q rounds the raw numbers to 13 decimal places
    let split_string = num_string.split(".");
    let new_string = split_string[0] + "." + split_string[1].substr(0, num_decimals);
    return parseFloat(new_string);
}

// Truncate the whole array of stats. array should be in the shape of a Q table.
function truncateStats(array, num_decimals) {
    if (num_decimals !== parseInt(num_decimals, 10))
        throw new Error("Expected an integer value.");
    if (num_decimals < 0)
        throw new Error("Expected a non-negative value.");

    let new_values = array.map(function (a) {
        return a.map(function (b) {
            return truncateDecimalNumber(b, num_decimals);
        });
    });
    return new_values;
}

See also