0

I have two arrays, percentiles and percentile_bounds and another int value total_score. Given the total score, I want to return the percentile in which this value is. Ie. I want to return a value from percentiles which corresponds to placing the total_score within the percentile_bounds. In python, I would do:

import numpy as np

percentiles = np.array([ 0,  5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95])
percentile_bounds = np.array([ 84, 104, 109, 115, 120, 123, 125, 127, 129, 132, 135, 136, 137, 139, 141, 145, 148, 151, 155, 159])

# example 1
total_score = 130
print(percentiles[np.where(percentile_bounds==(percentile_bounds[percentile_bounds<total_score][-1]))])
# 40

# example 2
total_score = 153
print(percentiles[np.where(percentile_bounds==(percentile_bounds[percentile_bounds<total_score][-1]))])
# 85

# example 3
total_score = 100
print(percentiles[np.where(percentile_bounds==(percentile_bounds[percentile_bounds<total_score][-1]))])
# 0

and I found a way in PHP (function sources: 1, 2) but it is very clumsy and long:

<?php
// Example 4
$total_score = 120;

$percentile_bounds = [84, 104, 109, 115, 120, 123, 125, 127, 129, 132, 135, 136, 137,139, 141, 145, 148, 151, 155, 159];
       
$percentiles = [0,  5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95];

// get the last element from percentile_bounds which is less than total_score
$value = end(array_filter($percentile_bounds, function ($x) use ($total_score) { return $x < $total_score; }));

// find its index
$key = array_search($value, $percentile_bounds);

// find the corresponding value in percentiles
echo "percentile: $percentiles[$key]";
// 15

Is there a better way of selecting the element from one array (percentiles) based on a position of an element (total_score) in the other array (percentile_bounds)?

Thanks

Note: it is not a duplicate of this.

Clarification: For total_score<=84 the result should be 0 (based on how percentiles work) but it also should not happen. In the code above I do not deal with this condition so it's ok not to but if the solution does, the better :). Thanks @jspit for pointing it out!

My Work
  • 2,143
  • 2
  • 19
  • 47
  • 1
    Why is this two arrays to begin with, why not one array that uses 0, 5, 10 etc. as _keys_? – CBroe Oct 28 '21 at 11:20
  • I guess that's just because I think in python and not PHP :D. I have data, then from them, I get the percentiles and the bounds which are two different arrays, then I go back to PHP (I do not have the data within PHP). I didn't think of it as a dictionary. – My Work Oct 28 '21 at 11:22
  • no result is defined for a total_score <= 84 – jspit Oct 28 '21 at 12:23
  • @jspit, thanks, good point, in that case, it should be 0 (based on how percentiles work) but it also should not happen. – My Work Oct 28 '21 at 12:28

2 Answers2

1

foreach would be more readable and performant approach compared to array_filter

$total_score = 153;

$percentile_bounds = [84, 104, 109, 115, 120, 123, 125, 127, 129, 132, 135, 136, 137, 139, 141, 145, 148, 151, 155, 159];

$percentiles = [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95];

foreach ($percentile_bounds as $key => $bound) {
    if ($bound >= $total_score) {
        echo 'percentile: ' . $percentiles[max(0, $key - 1)];
        break;
    }
}
User863
  • 19,346
  • 2
  • 17
  • 41
  • 1
    echo "percentile: ".($key > 0 ? $percentiles[$key-1] : "??"); – jspit Oct 28 '21 at 12:25
  • @jspit Good catch. thanks, buddy. we can use `max()` instead of ternary operator to resolve negative index – User863 Oct 28 '21 at 13:57
  • 1
    And I believe it's missing an equal sign: `if ($bound >= $total_score)` otherwise, the borders fall into the wrong interval. Also, would you have an explanation or reference for why it has better performance? Thanks – My Work Nov 01 '21 at 06:50
  • One more thing, it fails for `$total_score>=160` – My Work Nov 01 '21 at 08:32
1

You can do it with this line where array_keys gives us an array of keys of our filtered array. end and max do the same job in this case because we have a sorted array and want to return its last index. So finally $percentiles[$lastIndexKeyOfOurFilteredArray] is what we print out:

echo $percentiles[max(array_keys(array_filter($percentile_bounds,function ($x) use ($total_score){return $x<$total_score; })))]
My Work
  • 2,143
  • 2
  • 19
  • 47
keyhan
  • 522
  • 1
  • 7
  • 1
    While this code may answer the question, providing additional context regarding how and/or why it solves the problem would improve the answer's long-term value. You can find more information on how to write good answers in the help center: https://stackoverflow.com/help/how-to-answer . Good luck – nima Oct 28 '21 at 22:24
  • 1
    what @nima writes is exactly what I would like to ask you. Could you please provide some explanations to your code? How it works, what it does etc., exactly as nima writes. Thanks – My Work Oct 31 '21 at 10:17
  • Sure, `array_keys` gives us array of keys of our filtered array. `end` and `max` do the same job in this case because we have sorted array and want to return its last index. So finally `$percentiles[$lastIndexKeyOfOurFilteredArray]` is what we print out. – keyhan Oct 31 '21 at 11:29
  • One more issue with this: if `array_keys` is empty (the value is less than any in the`percentile_bounds`), the `max` function fails. How can it be fixed? The expected behaviour is to return 0. – My Work Jan 25 '22 at 10:30
  • PHP max function will throw an E_WARNING error and return a boolean false because of empty array input. PHP internal type-casting conversion will convert the boolean false to integer 0 so you already have your expected 0. You can skip the warning with an "@" {echo @percentiles[max(arr...}. Obviously its just a trick for preserving single-line style of the code and its better to use if/else statement for validating data before passing to function to avoid errors. – keyhan Jan 27 '22 at 16:03