[PATCH] Bug #24064: Standard deviation
| From: | Paul Hudson | Date: | Tue, 10 Feb 2004 00:30:02 +0000 |
| Subject: | [PATCH] Bug #24064: Standard deviation | ||
| Groups: | php.internals | ||
| Request: | Send a blank email to internals+get-7681@lists.php.net to get a copy of this message | ||
Hi there,
Bug #24064 (submitted by tularis@php.net) requests a standard deviation
function for PHP. I realise that any of you could implement this in 10
minutes, but according to the bug database it is still Open so I figured I
would give it a try myself!
There are probably a dozen errors in the code and/or places where it could
be better optimised, but I'm hoping one of you might be able to help with
that. So, the attached diff file implements the function array_std_dev(),
to calculate standard deviation using the deviation method.
With the function in place, standard deviation is calculated like this:
<?php
$scores = array(18,5,7,18,3,2,10);
print array_std_dev($score);
// prints 6.6833125519211
?>
My first attempt at implementing this was using an extra array to buffer the
deviations - this was more out of curiosity to see how the array stuff
works. Sadly, it caused PHP to segfault and I couldn't figure out why - can
any of you help me spot the brain fart? (I've attached the offending code
in bad_stddev_code.txt)
Yours,
Paul
PS: I'm not on the internals list, so I would appreciate it if you would CC
me on your reply.
/* {{{ proto mixed array_std_dev(array input) Returns the standard deviation of the array entries */ PHP_FUNCTION(array_std_dev) { zval **input, **entry, *entry_n, *squareddev; int argc = ZEND_NUM_ARGS(); HashPosition pos; double dval; double total; double mean; int numelements = 0; // note this is calcuated by hand, not using zend_hash_num_elements() double deviation; if (argc != 1 || zend_get_parameters_ex(argc, &input) == FAILURE) { WRONG_PARAM_COUNT; } if (Z_TYPE_PP(input) != IS_ARRAY) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "The argument should be an array"); return; } ZVAL_LONG(return_value, 0); total = 0; // step one: sum the values of the array for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_PP(input), &pos); zend_hash_get_current_data_ex(Z_ARRVAL_PP(input), (void **)&entry, &pos) == SUCCESS; zend_hash_move_forward_ex(Z_ARRVAL_PP(input), &pos)) { if (Z_TYPE_PP(entry) == IS_ARRAY || Z_TYPE_PP(entry) == IS_OBJECT) continue; entry_n = *entry; zval_copy_ctor(entry_n); convert_scalar_to_number(entry_n TSRMLS_CC); convert_to_double(entry_n); total += Z_DVAL_P(entry_n); // this is incremented by hand so that it doesn't count object and array elements as an element numelements++; } // step two: calculate the mean of the input array mean = total / numelements; // step three: get the deviation from the mean of each number in the input array, and add it to the squareddev array MAKE_STD_ZVAL(squareddev); array_init(squareddev); for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_PP(input), &pos); zend_hash_get_current_data_ex(Z_ARRVAL_PP(input), (void **)&entry, &pos) == SUCCESS; zend_hash_move_forward_ex(Z_ARRVAL_PP(input), &pos)) { if (Z_TYPE_PP(entry) == IS_ARRAY || Z_TYPE_PP(entry) == IS_OBJECT) continue; entry_n = *entry; zval_copy_ctor(entry_n); convert_scalar_to_number(entry_n TSRMLS_CC); convert_to_double(entry_n); Z_DVAL_P(entry_n) -= mean; zend_hash_next_index_insert(Z_ARRVAL_P(squareddev), entry_n, sizeof(zval *), NULL); } // step four: sum the squared deviation array total = 0; for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(squareddev), &pos); zend_hash_get_current_data_ex(Z_ARRVAL_P(squareddev), (void **)&entry, &pos) == SUCCESS; zend_hash_move_forward_ex(Z_ARRVAL_P(squareddev), &pos)) { entry_n = *entry; zval_copy_ctor(entry_n); convert_scalar_to_number(entry_n TSRMLS_CC); convert_to_double(entry_n); total += Z_DVAL_P(entry_n); } // step five: divide the sum of the squared deviation array by the number of elements - 1 total /= numelements - 1; Z_DVAL_P(return_value) = sqrt(total); } /* }}} */
/* {{{ proto mixed array_std_dev(array input) Returns the standard deviation of the array entries */ PHP_FUNCTION(array_std_dev) { zval **input, **entry, *entry_n, *squareddev; int argc = ZEND_NUM_ARGS(); HashPosition pos; double dval; double total; double mean; int numelements = 0; // note this is calcuated by hand, not using zend_hash_num_elements() double deviation; if (argc != 1 || zend_get_parameters_ex(argc, &input) == FAILURE) { WRONG_PARAM_COUNT; } if (Z_TYPE_PP(input) != IS_ARRAY) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "The argument should be an array"); return; } ZVAL_LONG(return_value, 0); total = 0; // step one: sum the values of the array for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_PP(input), &pos); zend_hash_get_current_data_ex(Z_ARRVAL_PP(input), (void **)&entry, &pos) == SUCCESS; zend_hash_move_forward_ex(Z_ARRVAL_PP(input), &pos)) { if (Z_TYPE_PP(entry) == IS_ARRAY || Z_TYPE_PP(entry) == IS_OBJECT) continue; entry_n = *entry; zval_copy_ctor(entry_n); convert_scalar_to_number(entry_n TSRMLS_CC); convert_to_double(entry_n); total += Z_DVAL_P(entry_n); // this is incremented by hand so that it doesn't count object and array elements as an element numelements++; } // step two: calculate the mean of the input array mean = total / numelements; // step three: get the deviation from the mean of each number in the input array, and add it to the squareddev array MAKE_STD_ZVAL(squareddev); array_init(squareddev); for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_PP(input), &pos); zend_hash_get_current_data_ex(Z_ARRVAL_PP(input), (void **)&entry, &pos) == SUCCESS; zend_hash_move_forward_ex(Z_ARRVAL_PP(input), &pos)) { if (Z_TYPE_PP(entry) == IS_ARRAY || Z_TYPE_PP(entry) == IS_OBJECT) continue; entry_n = *entry; zval_copy_ctor(entry_n); convert_scalar_to_number(entry_n TSRMLS_CC); convert_to_double(entry_n); Z_DVAL_P(entry_n) -= mean; zend_hash_next_index_insert(Z_ARRVAL_P(squareddev), entry_n, sizeof(zval *), NULL); } // step four: sum the squared deviation array total = 0; for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(squareddev), &pos); zend_hash_get_current_data_ex(Z_ARRVAL_P(squareddev), (void **)&entry, &pos) == SUCCESS; zend_hash_move_forward_ex(Z_ARRVAL_P(squareddev), &pos)) { entry_n = *entry; zval_copy_ctor(entry_n); convert_scalar_to_number(entry_n TSRMLS_CC); convert_to_double(entry_n); total += Z_DVAL_P(entry_n); } // step five: divide the sum of the squared deviation array by the number of elements - 1 total /= numelements - 1; Z_DVAL_P(return_value) = sqrt(total); } /* }}} */