At the end of the semester, quarter, or block, students can evaluate their courses, expressing their opinions about its various aspects. The evaluations typically include Likert-scale questions consisting of a statement and a ranked list of discrete responses (similar to a multiple-choice question). For example, "Did the instructor dress appropriately for class?" The evaluation device might give students four choices: (a) always, (b) most of the time, (c) seldom, and (d) never. Evaluations help institutions select and train qualified instructors and help instructors improve courses, but only if the data is valid. If students rush through the evaluation, making random selections, the evaluation data is meaningless.
The chi-square statistic (chi sounds like the beginning of kite) is the favored analytic tool for assessing this kind of data. The example has one question with four categories, "bins" or possible answers, called the degrees of freedom. To simplify the example, we assume that 100 students respond. If the responses are random, we expect students to select each choice about the same number of times. So, the expected frequency, fe, is 100/4 or 25 per bin. If the observed frequency, fo, is close to the expected frequency, that question is not significant (it does not demonstrate any useful information). But if the data are truly random, the observed and expected frequencies are unlikely to be exactly equal. The chi-square statistic attempts to distinguish between random and meaningful data with some degree of confidence.
|
|
The above formulas might look intimidating depending on your mathematical background. However, it will become clear once you see what each term means, how the problem relates to Chapter 7, and how we translate the formula into C++. The key is recognizing that fo is an array whose size is the same as the number of categories or bins in the problem (four in this example). Now that we know we are working with an array let's write the first version of the program.
#include <iostream> #include <cmath> using namespace std; int main() { // (a) int fo[100]; // observed frequencies int N = 0; // number of students completing the eval int k = 0; // number of categories (the degrees of freedom) cout << "Enter the observed frequencies for each category, -1 to end:" << endl; int f; // (b) cin >> f; while (f != -1 && k < 100) { fo[k++] = f; N += f; cin >> f; } double fe = (double)N / k; // (c) double sum = 0; // (d) for (int i = 0; i < k; i++) sum += pow(fo[i] - fe, 2); cout << "Chi-square = " << sum / fe << endl; // (e) return 0; }
When we run the program with the input: 40, 30, 20, and 10, the output is 20. To answer the ultimate question, "Are the observed frequencies significant?" we compare the chi-square to a critical value found in a table. If the calculated value is greater than the critical table value, the observed frequencies are significant and probably not just a set of random values.
The previous solution is short enough to fit in a single function easily. Nevertheless, separating the code into a client and supplier architecture has two advantages. First, it makes the chi-square calculation (the supplier) easier to reuse in other programs - perhaps unrelated to Likert scale questionnaires. Second, it leaves the client to determine the data's source (console, file, etc.) and statistic's ultimate use (output or further calculations).
lickert.cpp (Client) | chi2.cpp (Supplier) |
---|---|
#include <iostream> using namespace std; double chi2(int* fo, int k, double N); int main() { int fo[15]; int N = 0; int k = 0; cout << "Enter the observed frequencies, -1 to end:" << endl; while (k < 15) // (a) { int f; cin >> f; if (f == -1) break; fo[k++] = f; N += f; } cout << "Chi-square = " << chi2(fo, k, N) << endl; // (b) return 0; } |
#include <cmath> using namespace std; double chi2(int* fo, int k, double N) // (c) { double fe = N / k; // (d) double sum = 0; // (e) for (int i = 0; i < k; i++) sum += pow(fo[i] - fe, 2); return sum / fe; // (f) } |