Adam L.
Adam L.

Reputation: 187

How to assign a rank number to an array when ties exist

I am struggling to know where to start when trying to assign ranks to the numeric values in an array when there are ties. So, for example, I need to turn an array like the following:

myarray = (4,76,34,13,34)

into another array like:

myarray2 = (1,5,3.5,2,3.5)

Basically, when the same number occurs more than once in the array, the assigned rank to those numbers is the average of the ranks. So, instead of the two 34s being ranked 3 and 4 they both get assigned 3.5. Similarly, if there were 3 copies of 34 then the 3 assigned ranks would be divided by 3. Any help would be much appreciated!

Many thanks,

Adam

Upvotes: 5

Views: 2605

Answers (4)

Alix Axel
Alix Axel

Reputation: 154553

The accepted solution (and others too) seem to be way more complicated than they need to be:

function Rank($data) {
    $count = 0;
    $unique = $data; sort($unique);
    $unique = array_count_values($unique);

    foreach ($unique as $key => $frequency) {
        foreach (range(1, $frequency) as $i) {
            $unique[$key] += $count++;
        }

        $unique[$key] /= $frequency;
    }

    foreach ($data as $key => $value) {
        $data[$key] = $unique[$value];
    }

    return $data;
}

Example (demo):

print_r(Rank(array(4, 76, 34, 13, 34))); // 1; 5; 3.5; 2; 3.5
print_r(Rank(array(4, 76, 34, 13, 34, 34))); // 1; 6; 4; 2; 4; 4

Upvotes: 0

Sebastián Grignoli
Sebastián Grignoli

Reputation: 33432

I had fun with this one!

function rank($input) 
{
  $output = array();
  $ranking = $input; sort($ranking); $ranking = array_flip($ranking);
  $last_val = -1;
  foreach($ranking as $key => $val){
    $repetitions = ($val-$last_val-1);
    $last_val = $val;
    if($repetitions) {    
      $ranking[$key] = (($val*($repetitions+1))-($repetitions+1)*(($repetitions)/2))/($repetitions+1)+1 ;
    } else {
      $ranking[$key] = $val+1;
    }
  }
  foreach($input as $key => $val){
    $output[$key] = $ranking[$val];
  }
  return $output;
}

Use it like this:

$a = array(4,76,34,13,34);    
$c = rank($a);
print_r($c);

will output:

Array
(
    [0] => 1
    [1] => 5
    [2] => 3.5
    [3] => 2
    [4] => 3.5
)

wich is the same as:

Array(1, 5, 3.5, 2, 3.5)

as expected!

Upvotes: 2

pferate
pferate

Reputation: 2107

Here is one way to do it.

<?php
$myarray       = array(4,76,34,13,34);

$sorted_array  = $myarray;
$grouped_array = array();
sort($sorted_array);
foreach ($sorted_array as $rank => $entry) {
    // Initialize the entry if it doesn't already exist
    if (empty($grouped_array[$entry])) {
        $grouped_array[$entry]['count'] = 1.0;
        $grouped_array[$entry]['total'] = $rank + 1; // Account for 0-based array
    } else {
        $grouped_array[$entry]['count'] += 1.0;
        $grouped_array[$entry]['total'] += $rank + 1; // Account for 0-based array
    }
}
$myarray2 = array();
foreach ($myarray as $entry) {
    // Get the average
    $myarray2[] = $grouped_array[$entry]['total'] / $grouped_array[$entry]['count'];
}

Upvotes: 2

Bevan
Bevan

Reputation: 44307

I assume you also need to handle the cases where there are three or four or n values tied at the same rank.

I'm no PHP guru, but here's an approach (pseudo code) to defining a rank function:

define a = original array
define s = a.Sorted
define rank(n) = (s.FirstIndexOf(n) + s.LastIndexOf(n)) / 2

You may need to work a few examples on paper to convince yourself that this works even for triples and higher; it's reliant on s being sorted so that duplicates are adjacent.

Upvotes: 1

Related Questions