1 /* Confusion.cpp
2  *
3  * Copyright (C) 1993-2020 David Weenink
4  *
5  * This code is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or (at
8  * your option) any later version.
9  *
10  * This code is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this work. If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 /*
20  djmw 20010628
21  djmw 20020813 GPL header
22  djmw 20061214 Changed info to Melder_writeLine<x> format.
23  djmw 20070620 Latest modification.
24  djmw 20080521 +Confusion_drawAsNumbers
25  djmw 20110304 Thing_new
26  djmw 20111110 Use autostringvector
27 */
28 
29 #include "Confusion.h"
30 #include "Polygon_extensions.h"
31 #include "Matrix_extensions.h"
32 #include "TableOfReal_extensions.h"
33 #include "Collection_extensions.h"
34 #include "Distributions_and_Strings.h"
35 #include "NUM2.h"
36 
37 Thing_implement (Confusion, TableOfReal, 0);
38 
v_info()39 void structConfusion :: v_info () {
40 	double h, hx, hy, hygx, hxgy, uygx, uxgy, uxy, frac;
41 	integer nCorrect;
42 
43 	Confusion_getEntropies (this, & h, & hx, & hy, & hygx, & hxgy, & uygx, & uxgy, & uxy);
44 	Confusion_getFractionCorrect (this, & frac, & nCorrect);
45 	MelderInfo_writeLine (U"Number of rows: ", numberOfRows);
46 	MelderInfo_writeLine (U"Number of colums: ", numberOfColumns);
47 	MelderInfo_writeLine (U"Entropies (y is row variable):");
48 	MelderInfo_writeLine (U"  Total: ", h);
49 	MelderInfo_writeLine (U"  Y: ", hy);
50 	MelderInfo_writeLine (U"  X: ", hx);
51 	MelderInfo_writeLine (U"  Y given x: ", hygx);
52 	MelderInfo_writeLine (U"  X given y: ", hxgy);
53 	MelderInfo_writeLine (U"  Dependency of y on x; ", uygx);
54 	MelderInfo_writeLine (U"  Dependency of x on y: ", uxgy);
55 	MelderInfo_writeLine (U"  Symmetrical dependency: ", uxy);
56 	MelderInfo_writeLine (U"  Total number of entries: ", Confusion_getNumberOfEntries (this));
57 	MelderInfo_writeLine (U" Fraction correct: ", frac);
58 }
59 
Confusion_createFromStringses(Strings me,Strings thee)60 autoConfusion Confusion_createFromStringses (Strings me, Strings thee) {
61 	try {
62 		Melder_require (my numberOfStrings > 0 && thy numberOfStrings > 0, U"Both Strings should not be empty.");
63 
64 		autoConfusion him = Confusion_create (my numberOfStrings, thy numberOfStrings);
65 		for (integer irow = 1; irow <= my numberOfStrings; irow ++) {
66 			conststring32 label = my strings [irow].get();
67 			TableOfReal_setRowLabel (him.get(), irow, label);
68 		}
69 		for (integer icol = 1; icol <= thy numberOfStrings; icol ++) {
70 			conststring32 label = thy strings [icol].get();
71 			TableOfReal_setColumnLabel (him.get(), icol, label);
72 		}
73 		return him;
74 	} catch (MelderError) {
75 		Melder_throw (me, U": could not create Confusion with ", thee);
76 	}
77 }
78 
Confusion_create(integer numberOfStimuli,integer numberOfResponses)79 autoConfusion Confusion_create (integer numberOfStimuli, integer numberOfResponses) {
80 	try {
81 		autoConfusion me = Thing_new (Confusion);
82 		TableOfReal_init (me.get(), numberOfStimuli, numberOfResponses);
83 		return me;
84 	} catch (MelderError) {
85 		Melder_throw (U"Confusion not created.");
86 	}
87 }
88 
Confusion_createSimple(conststring32 labels_string)89 autoConfusion Confusion_createSimple (conststring32 labels_string) {
90 	try {
91 		autoSTRVEC labels = splitByWhitespace_STRVEC (labels_string);
92 		Melder_require (labels.size > 0, U"There should be at least one label.");
93 
94 		autoConfusion me = Confusion_create (labels.size, labels.size);
95 		integer ilabel = 1;
96 		for (integer itoken = 1; itoken <= labels.size; itoken ++) {
97 			const conststring32 token = labels [itoken].get();
98 			for (integer i = 1; i <= ilabel - 1; i ++) {
99 				if (Melder_equ (token, my rowLabels [i].get())) {
100 					Melder_throw (U"Label ", i, U" and ", ilabel, U" should not be equal.");
101 				}
102 			}
103 			TableOfReal_setRowLabel (me.get(), ilabel, token);
104 			TableOfReal_setColumnLabel (me.get(), ilabel, token);
105 			ilabel ++;
106 		}
107 		return me;
108 	} catch (MelderError) {
109 		Melder_throw (U"Simple Confusion not created.");
110 	}
111 }
112 
Categories_to_Confusion(Categories me,Categories thee)113 autoConfusion Categories_to_Confusion (Categories me, Categories thee) {
114 	try {
115 		Melder_require (my size == thy size, U"Both Categories should have the same number of items.");
116 
117 		autoCategories ul1 = Categories_selectUniqueItems (me);
118 		autoCategories ul2 = Categories_selectUniqueItems (thee);
119 		autoConfusion him = Confusion_create (ul1->size, ul2->size);
120 
121 		for (integer i = 1; i <= ul1->size; i ++) {
122 			const SimpleString s = ul1->at [i];
123 			TableOfReal_setRowLabel (him.get(), i, s -> string.get());
124 		}
125 		for (integer i = 1; i <= ul2->size; i ++) {
126 			const SimpleString s = ul2->at [i];
127 			TableOfReal_setColumnLabel (him.get(), i, s -> string.get());
128 		}
129 		for (integer i = 1; i <= my size; i ++) {
130 			const SimpleString myi = my at [i], thyi = thy at [i];
131 			Confusion_increase (him.get(), myi -> string.get(), thyi -> string.get());
132 		}
133 		return him;
134 	} catch (MelderError) {
135 		Melder_throw (me, U": no Confusion created.");
136 	}
137 }
138 
Confusion_getEntropies(Confusion me,double * out_h,double * out_hx,double * out_hy,double * out_hygx,double * out_hxgy,double * out_uygx,double * out_uxgy,double * out_uxy)139 void Confusion_getEntropies (Confusion me, double *out_h, double *out_hx, double *out_hy,
140 	double *out_hygx, double *out_hxgy, double *out_uygx, double *out_uxgy, double *out_uxy)
141 {
142 	NUMgetEntropies (my data.get(), out_h, out_hx,
143 			out_hy,	out_hygx, out_hxgy, out_uygx, out_uxgy, out_uxy);
144 }
145 
Confusion_increase(Confusion me,conststring32 stimulus,conststring32 response)146 void Confusion_increase (Confusion me, conststring32 stimulus, conststring32 response) {
147 	try {
148 		const integer stimulusIndex = TableOfReal_rowLabelToIndex (me, stimulus);
149 		Melder_require (stimulusIndex > 0,
150 			U"The stimulus name should be valid.");
151 
152 		const integer responseIndex = TableOfReal_columnLabelToIndex (me, response);
153 		Melder_require (responseIndex > 0,
154 			U"The response name should be valid.");
155 
156 		my data [stimulusIndex] [responseIndex] += 1.0;
157 	} catch (MelderError) {
158 		Melder_throw (me, U": not increased.");
159 	}
160 }
161 
Confusion_getValue(Confusion me,conststring32 stimulus,conststring32 response)162 double Confusion_getValue (Confusion me, conststring32 stimulus, conststring32 response) {
163 	const integer stimulusIndex = TableOfReal_rowLabelToIndex (me, stimulus);
164 	Melder_require (stimulusIndex > 0,
165 		U"The stimulus name should be valid.");
166 
167 	const integer responseIndex = TableOfReal_columnLabelToIndex (me, response);
168 	Melder_require (responseIndex > 0,
169 		U"The response name should be valid.");
170 
171 	return my data [stimulusIndex] [responseIndex];
172 }
173 
Confusion_getFractionCorrect(Confusion me,double * out_fraction,integer * out_numberOfCorrect)174 void Confusion_getFractionCorrect (Confusion me, double *out_fraction, integer *out_numberOfCorrect) {
175 	double fraction = undefined;
176 	integer numberOfCorrect = -1;
177 
178 	double c = 0.0, ct = 0.0;
179 	for (integer i = 1; i <= my numberOfRows; i ++) {
180 		for (integer j = 1; j <= my numberOfColumns; j ++) {
181 			if (! my rowLabels [i] || ! my columnLabels [j])
182 				return;
183 			ct += my data [i] [j];
184 			if (str32equ (my rowLabels [i].get(), my columnLabels [j].get()))
185 				c += my data [i] [j];
186 		}
187 	}
188 	if (ct != 0.0)
189 		fraction = c / ct;
190 	if (out_fraction)
191 		*out_fraction = fraction;
192 	numberOfCorrect = Melder_ifloor (c);
193 	if (out_numberOfCorrect)
194 		*out_numberOfCorrect = numberOfCorrect;
195 }
196 
197 /*************** Confusion_Matrix_draw ****************************************/
198 
Polygon_createPointer()199 static autoPolygon Polygon_createPointer () {
200 	try {
201 		constexpr integer numberOfPoints = 6;
202 		double x [numberOfPoints + 1] = { 0.0, 0.0, 0.9, 1.0, 0.9, 0.0, 0.0 };
203 		double y [numberOfPoints + 1] = { 0.0, 0.0, 0.0, 0.5, 1.0, 1.0, 0.0 };
204 		autoPolygon me = Polygon_create (numberOfPoints);
205 		for (integer i = 1; i <= numberOfPoints; i ++) {
206 			my x [i] = x [i];
207 			my y [i] = y [i];
208 		}
209 		return me;
210 	} catch (MelderError) {
211 		Melder_throw (U"Polygon not created.");
212 	}
213 }
214 
Polygon_drawInside(Polygon me,Graphics g)215 static void Polygon_drawInside (Polygon me, Graphics g) {
216 	Graphics_polyline (g, my numberOfPoints, & my x [1], & my y [1]);
217 }
218 
Confusion_Matrix_draw(Confusion me,Matrix thee,Graphics g,integer index,double lowerPercentage,double xmin,double xmax,double ymin,double ymax,bool garnish)219 void Confusion_Matrix_draw (Confusion me, Matrix thee, Graphics g, integer index, double lowerPercentage, double xmin, double xmax, double ymin, double ymax, bool garnish) {
220 	integer ib = 1, ie = my numberOfRows;
221 	if (index > 0 && index <= my numberOfColumns) {
222 		ib = ie = index;
223 	}
224 	Melder_require (thy ny == my numberOfRows,
225 		U"The number of stimuli should equal the number of rows in the matrix.");
226 
227 	if (xmax <= xmin)
228 		(void) Matrix_getWindowExtrema (thee, 1, 1, 1, thy ny, & xmin, & xmax);
229 	if (xmax <= xmin)
230 		return;
231 	if (ymax <= ymin)
232 		(void) Matrix_getWindowExtrema (thee, 2, 2, 1, thy ny, & ymin, & ymax);
233 	if (ymax <= ymin)
234 		return;
235 
236 	const double rmax = fabs (xmax - xmin) / 10.0;
237 	const double rmin = rmax / 10;
238 
239 	Graphics_setInner (g);
240 	Graphics_setWindow (g, xmin - rmax, xmax + rmax, ymin - rmax, ymax + rmax);
241 	Graphics_setTextAlignment (g, kGraphics_horizontalAlignment::CENTRE, Graphics_HALF);
242 	for (integer i = 1; i <= my numberOfRows; i ++) {
243 		Graphics_text (g, thy z [i] [1], thy z [i] [2], my rowLabels [i].get());
244 	}
245 	for (integer i = ib; i <= ie; i ++) {
246 		const double xSum = NUMsum (my data.row (i));
247 		if (xSum <= 0.0)
248 			continue;    /* no confusions */
249 		const double x1 = thy z [i] [1];
250 		const double y1 = thy z [i] [2];
251 		const double r = rmax * my data [i] [i] / xSum;
252 
253 		Graphics_circle (g, x1, y1, ( r > rmin ? r : rmin ));
254 
255 		for (integer j = 1; j <= my numberOfColumns; j ++) {
256 			const double x2 = thy z [j] [1], y2 = thy z [j] [2];
257 			const double perc =  100.0 * my data [i] [j] / xSum;
258 			const double dx = x2 - x1, dy = y2 - y1;
259 			const double alpha = atan2 (dy, dx);
260 
261 			if (perc == 0.0 || perc < lowerPercentage || j == i)
262 				continue;
263 			xmin = x1;
264 			xmax = x2;
265 			if (x2 < x1) {
266 				xmin = x2;
267 				xmax = x1;
268 			}
269 			ymin = y1;
270 			xmax = y2;
271 			if (y2 < y1) {
272 				ymin = y2;
273 				ymax = y1;
274 			}
275 			autoPolygon p = Polygon_createPointer();
276 			double xs = hypot (dx, dy) - 2.2 * r;
277 			if (xs < 0.0)
278 				xs = 0.0;
279 			const double ys = perc * rmax / 100.0;
280 			Polygon_scale (p.get(), xs, ys);
281 			Polygon_translate (p.get(), x1, y1 - ys / 2);
282 			Polygon_rotate (p.get(), alpha, x1, y1);
283 			Polygon_translate (p.get(), 1.1 * r * cos (alpha), 1.1 * r * sin (alpha));
284 			Polygon_drawInside (p.get(), g);
285 		}
286 	}
287 	Graphics_unsetInner (g);
288 	if (garnish) {
289 		Graphics_drawInnerBox (g);
290 		Graphics_marksBottom (g, 2, true, true, false);
291 		if (ymin * ymax < 0.0) {
292 			Graphics_markLeft (g, 0.0, true, true, true, nullptr);
293 		}
294 		Graphics_marksLeft (g, 2, true, true, false);
295 		if (xmin * xmax < 0.0) {
296 			Graphics_markBottom (g, 0.0, true, true, true, nullptr);
297 		}
298 	}
299 }
300 
Confusion_difference(Confusion me,Confusion thee)301 autoMatrix Confusion_difference (Confusion me, Confusion thee) {
302 	try {
303 		/* categories must be the same too*/
304 		Melder_require (my numberOfColumns == thy numberOfColumns && my numberOfRows == thy numberOfRows,
305 			U"The dimensions should be equal.");
306 
307 		autoMatrix him = Matrix_create (0.5, my numberOfColumns + 0.5, my numberOfColumns, 1.0, 1.0, 0.5, my numberOfRows + 0.5, my numberOfRows, 1.0, 1.0);
308 
309 		his z.all()  <<=  my data.all()  -  thy data.all();
310 
311 		return him;
312 	} catch (MelderError) {
313 		Melder_throw (U"Matrix not created from two Confusions.");
314 	}
315 }
316 
Confusion_getNumberOfEntries(Confusion me)317 integer Confusion_getNumberOfEntries (Confusion me) {
318 	const double total = NUMsum (my data.get());
319 	return Melder_ifloor (total);
320 }
321 
create_index(constSTRVEC const & s,constSTRVEC const & ref)322 static autoINTVEC create_index (constSTRVEC const& s, constSTRVEC const& ref) {
323 	autoINTVEC index = raw_INTVEC (s.size);
324 	for (integer i = 1; i <= s.size; i ++) {
325 		integer indxj = 0;
326 		for (integer j = 1; j <= ref.size; j ++) {
327 			if (str32equ (s [i], ref [j])) {
328 				indxj = j;
329 				break;
330 			}
331 		}
332 		index [i] = indxj;
333 	}
334 	return index;
335 }
336 
Confusion_condense(Confusion me,conststring32 search,conststring32 replace,integer maximumNumberOfReplaces,bool use_regexp)337 autoConfusion Confusion_condense (Confusion me, conststring32 search, conststring32 replace,
338 	integer maximumNumberOfReplaces, bool use_regexp) {
339 	try {
340 		integer nmatches, nstringmatches;
341 		Melder_require (my rowLabels && my columnLabels,
342 			U"Both row and column labels should be present.");
343 
344 		autoSTRVEC rowLabels = string32vector_searchAndReplace (my rowLabels.get(),
345 				search, replace, maximumNumberOfReplaces, & nmatches, & nstringmatches, use_regexp);
346 
347 		autoSTRVEC columnLabels = string32vector_searchAndReplace (my columnLabels.get(),
348 				search, replace, maximumNumberOfReplaces, & nmatches, & nstringmatches, use_regexp);
349 
350 		autoStrings srow = Thing_new (Strings);
351 		srow -> numberOfStrings = my numberOfRows;
352 		srow -> strings = std::move (rowLabels);
353 
354 		autoStrings scol = Thing_new (Strings);
355 		scol -> numberOfStrings = my numberOfColumns;
356 		scol -> strings = std::move (columnLabels);
357 
358 		/* Find dimension of new Confusion */
359 		autoDistributions dcol = Strings_to_Distributions (scol.get());
360 		const integer nresp = dcol -> numberOfRows;
361 
362 		autoDistributions drow = Strings_to_Distributions (srow.get());
363 		const integer nstim = drow -> numberOfRows;
364 
365 		autoConfusion thee = Confusion_create (nstim, nresp);
366 
367 		thy rowLabels.all()  <<=  drow -> rowLabels.all();
368 		thy columnLabels.all()  <<=  dcol -> rowLabels.all();
369 
370 		autoINTVEC rowIndex = create_index (srow -> strings.get(), drow -> rowLabels.get());
371 		autoINTVEC columnIndex = create_index (scol -> strings.get(), dcol -> rowLabels.get());
372 
373 		for (integer i = 1; i <= my numberOfRows; i ++)
374 			for (integer j = 1; j <= my numberOfColumns; j ++)
375 				thy data [rowIndex [i]] [columnIndex [j]] += my data [i] [j];
376 		return thee;
377 	} catch (MelderError) {
378 		Melder_throw (me, U": not condensed.");
379 	}
380 }
381 
TableOfReal_to_Confusion(TableOfReal me)382 autoConfusion TableOfReal_to_Confusion (TableOfReal me) {
383 	try {
384 		Melder_require (TableOfReal_isNonNegative (me),
385 			U"No cell in the table should be negative.");
386 		autoConfusion thee = Thing_new (Confusion);
387 		my structTableOfReal :: v_copy (thee.get());
388 		return thee;
389 	} catch (MelderError) {
390 		Melder_throw (me, U": not converted to Confusion.");
391 	}
392 }
393 
Confusion_group(Confusion me,conststring32 labels,conststring32 newLabel,integer newpos)394 autoConfusion Confusion_group (Confusion me, conststring32 labels, conststring32 newLabel, integer newpos) {
395 	try {
396 		autoConfusion stim = Confusion_groupStimuli (me, labels, newLabel, newpos);
397 		autoConfusion thee = Confusion_groupResponses (stim.get(), labels, newLabel, newpos);
398 		return thee;
399 	} catch (MelderError) {
400 		Melder_throw (me, U": not grouped.");
401 	}
402 }
403 
Confusion_groupStimuli(Confusion me,conststring32 labels_string,conststring32 newLabel,integer newpos)404 autoConfusion Confusion_groupStimuli (Confusion me, conststring32 labels_string, conststring32 newLabel, integer newpos) {
405 	try {
406 		autoSTRVEC labels = splitByWhitespace_STRVEC (labels_string);
407 		const integer ncondense = labels.size;
408 		autoINTVEC irow = to_INTVEC (my numberOfRows);
409 
410 		for (integer itoken = 1; itoken <= labels.size; itoken ++) {
411 			const conststring32 token = labels [itoken].get();
412 			for (integer i = 1; i <= my numberOfRows; i ++) {
413 				if (Melder_equ (token, my rowLabels [i].get())) {
414 					irow [i] = 0;
415 					break;
416 				}
417 			}
418 		}
419 		integer nfound = 0;
420 		for (integer i = 1; i <= my numberOfRows; i ++) {
421 			if (irow [i] == 0)
422 				nfound ++;
423 		}
424 		Melder_require (nfound > 0,
425 			U"The stimulus labels are invalid.");
426 
427 		if (nfound != ncondense)
428 			Melder_warning (U"One or more of the given stimulus labels are suspect.");
429 		const integer newnstim = my numberOfRows - nfound + 1;
430 		Melder_clip (1_integer, & newpos, newnstim);
431 		autoConfusion thee = Confusion_create (newnstim, my numberOfColumns);
432 		thy columnLabels.all()  <<=  my columnLabels.all();
433 		TableOfReal_setRowLabel (thee.get(), newpos, newLabel);
434 		integer inewrow = 1;
435 		for (integer i = 1; i <= my numberOfRows; i ++) {
436 			integer rowpos = newpos;
437 			if (irow [i] > 0) {
438 				if (inewrow == newpos)
439 					inewrow ++;
440 				rowpos = inewrow;
441 				inewrow ++;
442 				TableOfReal_setRowLabel (thee.get(), rowpos, my rowLabels [i].get());
443 			}
444 			thy data.row (rowpos)  +=  my data.row (i);
445 		}
446 		return thee;
447 	} catch (MelderError) {
448 		Melder_throw (me, U": stimuli not grouped.");
449 	}
450 }
451 
Confusion_groupResponses(Confusion me,conststring32 labels_string,conststring32 newLabel,integer newpos)452 autoConfusion Confusion_groupResponses (Confusion me, conststring32 labels_string, conststring32 newLabel, integer newpos) {
453 	try {
454 		autoSTRVEC labels = splitByWhitespace_STRVEC (labels_string);
455 		const integer ncondense = labels.size;
456 		autoINTVEC icol = to_INTVEC (my numberOfColumns);
457 
458 		for (integer itoken = 1; itoken <= labels.size; itoken ++) {
459 			const conststring32 token = labels [itoken].get();
460 			for (integer i = 1; i <= my numberOfColumns; i ++) {
461 				if (Melder_equ (token, my columnLabels [i].get())) {
462 					icol [i] = 0;
463 					break;
464 				}
465 			}
466 		}
467 		integer nfound = 0;
468 		for (integer i = 1; i <= my numberOfColumns; i ++) {
469 			if (icol [i] == 0)
470 				nfound ++;
471 		}
472 		Melder_require (nfound > 0,
473 			U"The response labels are invalid.");
474 
475 		if (nfound != ncondense)
476 			Melder_warning (U"One or more of the given response labels are suspect.");
477 		const integer newnresp = my numberOfColumns - nfound + 1;
478 		Melder_clip (1_integer, & newpos, newnresp);
479 		autoConfusion thee = Confusion_create (my numberOfRows, newnresp);
480 		thy rowLabels.all()  <<=  my rowLabels.all();
481 		TableOfReal_setColumnLabel (thee.get(), newpos, newLabel);
482 		integer inewcol = 1;
483 		for (integer i = 1; i <= my numberOfColumns; i ++) {
484 			integer colpos = newpos;
485 			if (icol [i] > 0) {
486 				if (inewcol == newpos)
487 					inewcol ++;
488 				colpos = inewcol;
489 				inewcol ++;
490 				TableOfReal_setColumnLabel (thee.get(), colpos, my columnLabels [i].get());
491 			}
492 			thy data.column (colpos)  +=  my data.column (i);
493 		}
494 		return thee;
495 	} catch (MelderError) {
496 		Melder_throw (me, U": responses not grouped.");
497 	}
498 }
499 
Confusion_to_TableOfReal_marginals(Confusion me)500 autoTableOfReal Confusion_to_TableOfReal_marginals (Confusion me) {
501 	try {
502 		autoTableOfReal thee = TableOfReal_create (my numberOfRows + 1, my numberOfColumns + 1);
503 
504 		thy data.part(1, my numberOfRows, 1, my numberOfColumns)  <<=  my data.get();
505 		autoVEC columnSums = columnSums_VEC (my data.get());
506 		thy data.row (my numberOfRows + 1).part (1, my numberOfColumns)  <<=  columnSums.get();
507 		autoVEC rowSums = rowSums_VEC (my data.get());
508 		thy data.column (my numberOfColumns + 1).part (1, my numberOfRows)  <<=  rowSums.get();
509 
510 		thy data [my numberOfRows + 1] [my numberOfColumns + 1] = NUMsum (rowSums.get());
511 
512 		thy rowLabels.part (1, my numberOfRows)  <<=  my rowLabels.all();
513 		thy columnLabels.part (1, my numberOfColumns)  <<=  my columnLabels.all();
514 		return thee;
515 	} catch (MelderError) {
516 		Melder_throw (me, U": table with marginals not created.");
517 	}
518 }
519 
Confusion_drawAsNumbers(Confusion me,Graphics g,bool marginals,int iformat,int precision)520 void Confusion_drawAsNumbers (Confusion me, Graphics g, bool marginals, int iformat, int precision) {
521 	TableOfReal thee = me;
522 	autoTableOfReal athee;
523 	if (marginals) {
524 		athee = Confusion_to_TableOfReal_marginals (me);
525 		thee = athee.get();
526 	}
527 	TableOfReal_drawAsNumbers (thee, g, 1, thy numberOfRows, iformat, precision);
528 }
529 
530 /* End of file Confusion.cpp */
531