7

I have the following array:

(
    [25] => 1
    [26] => 3
    [10] => 2
    [24] => 1
)

It was created using the array_count_values() function in PHP.

Actual original array was something like this, before array_count_values...

Array
(
    [0] => 26
    [1] => 
    [2] => 18
    [3] => 28
    [4] => 22
    [5] => 21
    [6] => 26
    [7] => 
    [8] => 
    [9] => 
    [10] => 
    [11] => 
    [12] =>
    [13] =>
    [14] =>
    [15] =>
    [16] =>
    [17] =>
    [18] =>
    [19] =>
    [20] =>
)

These are ages, so how can I group these into age groups?

Lets say I want the following age groups: <= 18 19-26 27-32 > 32

It is supposed to look:

(
[<= 18] => 1
[19-26] => 4
[27-32] => 2
[ > 32] => 1
)

Is there a ready function for this?

My solution: 1 tedious way would be to create variables of age groups. Than foreach and increase variable ++ for specific age group if key matches range ($min <= $value) && ($value <= $max)...

3
  • 1
    There's no built-in PHP function to sort an array into groups. Commented Mar 15, 2013 at 14:07
  • 1
    Are you sure your expected output is correct? According to your array_count_values() output, you have 2 people in the < 18 category, 5 people in the 19-26 category, and nobody in the remaining categories. Commented Mar 15, 2013 at 14:23
  • @nickb output is wrong, I made a mistake pasting here output from the top of my mind... Commented Mar 15, 2013 at 14:30

4 Answers 4

10

Interesting problem. Here is my solution. First, define an array of age ranges:

$age_ranges = array(
    array( 0, 18),
    array( 19, 26),
    array( 27, 32),
    array( 33, 150) // I use 150 as the "max" age
);

Then, we have your array_count_values() output:

$array_count_values = array( 25 => 1, 26 => 3, 10 => 2, 24 => 1); // From OP

Now, we create an array of all ages, where the keys are the age, and the values are the number of people with that age. It needs to be sorted by its keys for the next step.

$all_ages = $array_count_values + array_fill( 0, 150, 0);
ksort( $all_ages);

Finally, I loop over all the age ranges, slice off the age range from the $all_ages array, and sum their values to produce an array of the age ranges, with its values corresponding to how many people fell into that age range.

$result = array();
foreach( $age_ranges as $range) {
    list( $start, $end) = $range;
    $result["$start-$end"] = array_sum( array_slice( $all_ages, $start, $end - $start + 1));
}

A print_r( $result); yields the following output:

Array
(
    [0-18] => 2
    [19-26] => 5
    [27-32] => 0
    [33-150] => 0
)

Edit: Since you still have access to your original array, you can just calculate how many "unknowns" you had at the very end:

$result['unknown'] = count( array_filter( $original_array,  function( $el) { 
    return empty( $el); 
}));
Sign up to request clarification or add additional context in comments.

7 Comments

The OP may use array_map() to avoid the "foreach loop", +1 nickb !
@HamZaDzCyberDeV: I don't see how array_map would help here. Plus the foreach is probably faster.
What about undefined results? I'm sorry I didn't write this in OP. But I've to account for empty keys as they are the users who didn't specify age. But for pie chart I'm doing it would be important to include also that.
@Sando - Is that coming back in your array_count_values() array?
@SandroDzneladze i updated my answer to account for unknowns.
|
8

This sounds like a perfect case of map-reduce.

Map step: turn each age into one of the 4 ranges.

Reduce step: turn the range into a result array of ranges.

Let's try it out:

$map = function($age) {
    if (!$age) {
        return 'unknown';
    } elseif ($age <= 18) {
        return '<= 18';
    } elseif ($age <= 26) {
        return '19-26';
    } elseif ($age <= 32) {
        return '26-32';
    }
    return '> 32';
}

So basically here, we're just turning each age into a string representation of the range it represents. So now we'll have an array of ranges, so we need to reduce that out to a summary (add up all of those ranges):

And then the reduce function:

$reduce = function($result, $age) {
    $result[$age]++;
    return $result;
}

This is quite simple. If you wanted to support dynamic ranges, then you'd have some logic in there to check if the age is not set (and then initialize it to 0)...

So now, putting it all together:

$array = array(12, 63, 24, 34, 12, 10, 19,); // Your ages
$data = array_map(function($age) {
    if (!$age) {
        return 'unknown';
    } elseif ($age <= 18) {
        return '<= 18';
    } elseif ($age <= 26) {
        return '19-26';
    } elseif ($age <= 32) {
        return '26-32';
    }
    return '> 32';
}, $array);
$result = array_reduce($data, function($result, $age) {
    $result[$age]++;
    return $result;
}, array('unknown' => 0, '<= 18' => 0, '19-26' => 0, '26-32' => 0, '> 32' => 0));

Which then yields:

array(4) { 
    ["unknown"]=> int(0)
    ["<= 18"]=> int(3) 
    ["19-26"]=> int(2) 
    ["26-32"]=> int(0) 
    ["> 32"]=> int(2) 
}

Comments

1

If you wanted to just use the array you had before array_count_values(), you could use

$ages = array(
    0 => 26,
    1 => 18,
    2 => 28,
    3 => 22,
    4 => null,
    5 => 21,
    6 => 26,
    7 => null);

create a blank array to fill up

$final = array(
    'unknown' => 0,
    '18' => 0,
    '19_26' => 0,
    '27_32' => 0,
    '32' => 0,
);

array_walk($ages, function($age, $i, $final) {
    if (!$age) { $final['unknown']++; return; }
    if ($age <= 18) { $final['18']++; return; }
    if ($age <= 26) { $final['19_26']++; return; }
    if ($age <= 32) { $final['27_32']++; return; }
    if ($age > 32) { $final['32']++; return; }
}, &$final);

output from $final:

array (size=5)
  'unknown' => int 2
  18 => int 1
  '19_26' => int 4
  '27_32' => int 1
  32 => int 0

Comments

1

After seeing all those solutions, i thought why not using Regex with the original array ?

$ages = array(25, 26, 10, 24, 10, 26, 26, 32, 32, 54, 84, 4, 18, 5, 98, 27);
$string = '#'. implode('#', $ages) . '#';
preg_match_all('/(#[0-9]#|#1[0-7]#)|(#1[8-9]#|#2[0-6]#)|(#2[7-9]#|#3[0-2]#)|(#3[3-9]#|#[4-9][0-9]#|#1[0-5][0-9]#)/', $string, $groups);
$age_ranges = array('0-17' => count(array_filter($groups[1])), '18-26' => count(array_filter($groups[2])), '27-32'
 => count(array_filter($groups[3])), '33-159' => count(array_filter($groups[4])));

print_r($age_ranges);

Output:

Array
(
    [0-17] => 2
    [18-26] => 3
    [27-32] => 1
    [33-159] => 2
)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.