I'm building a little app that analyze ebay historical prices of sold items
and for some keywords/items the range is very wide because the search is too broad or simply wrong, infected by item not properly related
eg.
search prices for iphone the results include either the phone, but
also the charger and accessories/unrelated items which adulterate the prices data...
so i have a range that goes form $5 fro a charger and 500$ for an
iphone
so, given that I will try to improve the search on my side, i'm wondering if there is math calculation to exclude the outliers
say I have
$1200
$549
$399
$519
$9
$599
$549
$9
$499
$399
$519
$99
$5
$5
how to i get the price range to be $300-$600 instead of $10-$800 or so...
her ebelow the current php im using...not sure if is the best
function remove_outliers($dataset, $magnitude = 1)
{
$count = count($dataset);
$mean = array_sum($dataset) / $count; // Calculate the mean
$deviation = sqrt(array_sum(array_map("sd_square", $dataset, array_fill(0, $count, $mean))) / $count) * $magnitude; // Calculate standard deviation and times by magnitude
return array_filter($dataset, function ($x) use ($mean, $deviation) {return ($x <= $mean + $deviation && $x >= $mean - $deviation);}); // Return filtered array of values that lie within $mean +- $deviation.
}
function sd_square($x, $mean)
{
return pow($x - $mean, 2);
}
function calculate_median($arr)
{
sort($arr);
$count = count($arr);
$middleval = floor(($count - 1) / 2);
if ($count % 2) {
$median = $arr[$middleval];
} else {
$low = $arr[$middleval];
$high = $arr[$middleval + 1];
$median = (($low + $high) / 2);
}
return $median;
}
$prices = remove_outliers($prices); //$prices is the array with all the prices stored
$trend = calculate_median($prices);
$trend = round(($trend));
$min = round(min($prices));
$max = round(max($prices));
I find this function useful. The $cleaness variable will give granularity
/**
* Returns an average value from a dirt list of numbers.
*
* #require
*
* $numbers = an array of numbers
* $cleaness = a percentage value
*
* #return integer
* an average value from a cleaner list.
*/
public function CleanAverage ( $numbers, $cleaness ) {
// A
$extremes_to_remove = floor(count($numbers)/100*$cleaness);
if ($extremes_to_remove < 2) {$extremes_to_remove = 2;}
// B
sort ($numbers) ;
// C
//remove $extremes from top
for ($i = 0; $i < ($extremes_to_remove/2); $i++) {
array_pop($numbers);
}
// D
// revers order
rsort($numbers);
// E
//remove $extremes from top
for ( $i = 0; $i < ($extremes_to_remove/2); $i++ ) {
array_pop($numbers);
}
// F
// average
$average = array_sum($numbers)/count($numbers);
return $average;
}
Related
i'm trying to increase a variable value depending on the other variable value for example:
i have a variable called $totalhousesleft...
i want to set a price depending on how many $totalhousesleft i have...
everytime the totalhousesleft is down by 10, i want to increase the variable $currentprice by 1.
the starting value of $totalhouses left is 8000 and every time it goes down by 10, i set the $currentprice +1... the starting value of current price is 9...
something like:
If ($totalhousesleft >= 8000) {$currentprice = 9; $sellingprice = 8;}
If ($totalhousesleft >= 7990) {$currentprice = 10; $sellingprice = 9;}
If ($totalhousesleft >= 7980) {$currentprice = 11; $sellingprice = 10;}
If ($totalhousesleft >= 7970) {$currentprice = 12; $sellingprice = 11;}
ALL THE WAY DOWN UNTIL HOUSES LEFT IS 1. If someone can please show me a loop or a shorter code i would really appreciate it!
#elias-soares answer is close, but is missing ceil...and an explanation.
foreach ( [8000, 7995, 7990, 7985, 7980, 7975, 7970, 7965] as $totalhousesleft ) {
$currentprice = 9 + ((ceil(800 - ((min(8000, $totalhousesleft)) / 10))) * 1);
$sellingprice = $currentprice - 1;
}
Try it here: https://onlinephp.io/c/68196
Let's break down how to get $currentprice:
//$currentprice = ceil(9 + (800 - (min(8000, $totalhousesleft) / 10)));
// get the lesser of 8000, or $totalhousesleft
// in other words, 8000 is the maximum number to calculate
$totalhousesleft = min(8000, $totalhousesleft);
// divide total houses left into number of tenth units
$tenth = $totalhousesleft / 10;
// since the price increases when the number of tenth units decreases,
// the unit factor is the difference between the max possible tenths
// and tenths of the current total houses left
$tenthunit = 800 - $tenth;
// tenth unit is fractional for values not evenly divisible by 10,
// so round up
$tenthroundup = ceil($tenthunit);
// multiply the number of tenth units with the price per unit
$pricepertenth = $tenthroundup * 1; // 1 currency per tenth unit
// add the price per tenth cost to the base cost (9 currency)
$currentprice = 9 + $pricepertenth;
Bonus: this can be implemented in a function:
function getPrices ($totalhousesleft, $baseprice = 9, $discount = 1, $priceperunit = 1, $maxtotal = 8000, $units = 10) {
$currentprice = $baseprice + ((ceil(($maxtotal / $units) - ((min($maxtotal, $totalhousesleft)) / $units))) * $priceperunit);
return [$currentprice, $currentprice - $discount];
}
foreach ( [8000, 7995, 7990, 7985, 7980, 7975, 7970, 7965] as $totalhousesleft ) {
list($currentprice, $sellingprice) = getPrices($totalhousesleft);
}
Try it here: https://onlinephp.io/c/2672b
A for or while loop could be used for this. I'd use for:
$iteration = 0;
for($x = 8000; $x > 0; $x = $x - 10){
if(empty($iteration)) {
$iteration = $x/1000;
}
if ($totalhousesleft >= $x) {
$currentprice = $iteration;
$sellingprice = $currentprice + 1;
break;
}
$iteration++;
}
if(empty($currentprice)){
$currentprice = $iteration;
$sellingprice = $currentprice + 1;
}
This iterates over until a match is found then breaks out of the looping. The prices are based on the iteration it is in.
Demo link: https://3v4l.org/Mm432 (updated for edge cases 0-9)
You can use Math.
$currentprice = 9 + (800 - (min(8000,$totalhousesleft)/10));
$sellingprice = $currentprice - 1;
I need to calculate total cost of items, based on ranges:
if ($itemCount <11)
{
$cost_for_range = 1;
}
if ($itemCount <26 && $itemCount >=11)
{
$cost_for_range = 0.75;
}
if ($itemCount <50 && $itemCount >=26)
{
$cost_for_range = 0.55;
}
Thing is - I need to count final cost taking earlier levels into consideration - eg if the cart has 30 items, the price would be:
first 10 at $1 = $10
another 15 at $0.75 = $11.25
another 5 at $0.55 = $2.75
so total would be $24.
How to calculate this in php?
Try this :
$itemCount = 25;
$total = 0;
for ( $i = 0 ; $i < $itemCount ; $i++ ) {
if ( $i < 10 ) {
$total += 1;
} else if ( $i < 25 ) {
$total += 0.75;
} else if ( $i < 50 ) {
$total += 0.55;
} else {
$total += 0; // price if more than 50
}
}
Having some fun using min and max to do the various ranges...
function calculateCost ( int $count, float $cost ) {
$total = min(10, $count) * $cost;
$total += (min(15, max(0, $count - 10)) * ($cost * 0.75));
$total += (max(0, $count - 25) * ($cost * 0.55));
return $total;
}
the second calculation uses max(0, $count - 10), so this takes the first 10 off, but ensures it doesn't go negative. Then it uses the min(15, max(0, $count - 10)) to take the lowest of 15 or the number left (so with 30, this will be 15).
The last one is just the max of the count - 25 or 0, so for 30, this is 5.
Use it with...
echo calculateCOst( 30, 1 );
gives 24
For a universal solution, it is good to define the discounts in an array. This solution is then ready for extensions and changes.
$rebate = [10 => 1.0, 15 => 0.75, PHP_INT_MAX => 0.55];
The number and the discount array are transferred to the function that calculates the cost factor. The calculation itself is realized with a simple forech loop with termination condition.
function calcCostFactor(int $count, array $rebate){
$factor = 0.0;
foreach($rebate as $number => $val){
$factor += min($count,$number) * $val;
$count -= $number;
if($count <= 0) break;
}
return $factor;
}
example:
$factor = calcCostFactor(30, $rebate);
Try it self.
I am trying to expand and improve the round-robin algorithm from 1v1 group to a 1v1v1v1 group(something like free for all). I've made the function itself to do the schedule, but when I tried to expand it, some teams repetead. For example, I have 16 teams and I want to have 5 rounds, team 1 appears 7 times in 5 rounds and team2 appears 3 times in 5 rounds. I need them to appear 5 times at most.I really can't understand how I can do it. Any advice is welcomed and links.
function make_schedule(array $teams, int $rounds = null, bool $shuffle = true, int $seed = null): array
{
$teamCount = count($teams);
if($teamCount < 4) {
return [];
}
//Account for odd number of teams by adding a bye
if($teamCount % 2 === 1) {
array_push($teams, null);
$teamCount += 1;
}
if($shuffle) {
//Seed shuffle with random_int for better randomness if seed is null
srand($seed ?? random_int(PHP_INT_MIN, PHP_INT_MAX));
shuffle($teams);
} elseif(!is_null($seed)) {
//Generate friendly notice that seed is set but shuffle is set to false
trigger_error('Seed parameter has no effect when shuffle parameter is set to false');
}
$quadTeamCount = $teamCount / 4;
if($rounds === null) {
$rounds = $teamCount - 1;
}
$schedule = [];
for($round = 1; $round <= $rounds; $round += 1) {
$matchupPrev = null;
foreach($teams as $key => $team) {
if($key >= $quadTeamCount ) {
break;
}
$keyCount = $key + $quadTeamCount;
$keyCount2 = $key + $quadTeamCount + 1;
$keyCount3 = $key + $quadTeamCount + 2;
$team1 = $team;
$team2 = $teams[$keyCount];
$team3 = $teams[$keyCount2];
$team4 = $teams[$keyCount3];
//echo "<pre>Round #{$round}: {$team1} - {$team2} - {$team3} - {$team4} == KeyCount: {$keyCount} == KeyCount2: {$keyCount2} == KeyCount3: {$keyCount3}</pre>";
//Home-away swapping
$matchup = $round % 2 === 0 ? [$team1, $team2, $team3, $team4 ] : [$team2, $team1, $team4, $team3];
$schedule[$round][] = $matchup ;
}
rotate($teams);
}
return $schedule;
}
Rotate function:
function rotate(array &$items)
{
$itemCount = count($items);
if($itemCount < 3) {
return;
}
$lastIndex = $itemCount - 1;
/**
* Though not technically part of the round-robin algorithm, odd-even
* factor differentiation included to have intuitive behavior for arrays
* with an odd number of elements
*/
$factor = (int) ($itemCount % 2 === 0 ? $itemCount / 2 : ($itemCount / 2) + 1);
$topRightIndex = $factor - 1;
$topRightItem = $items[$topRightIndex];
$bottomLeftIndex = $factor;
$bottomLeftItem = $items[$bottomLeftIndex];
for($i = $topRightIndex; $i > 0; $i -= 1) {
$items[$i] = $items[$i - 1];
}
for($i = $bottomLeftIndex; $i < $lastIndex; $i += 1) {
$items[$i] = $items[$i + 1];
}
$items[1] = $bottomLeftItem;
$items[$lastIndex] = $topRightItem;
}
For example:
If I set rounds to 5, every team play 5 matches.
Array example Screenshot
Dealing with the 5th round:
Well, after I thought for a bit, maybe there isn't a way for them to play without repeatence, but if it is lowered to minumum, like every team should play 5 times only - this means once a round. That's what I meant. And what I meant under 'they repeat' is that there are like: 16 teams, 5 rounds and some teams are going like 7 times for all these rounds and other teams are going 3 times for these 5 rounds. I want to avoid this and to make every team play 5 rounds at most.
Your foreach() with the selection of the other 3 teams is wrong. One of them have to make steps with a multiple of 4. If you don't, you will select the teams at the beginning more than one and don't select the teams at the end of the array at all. This will result in wrong team matchups like this (teams are letters here):
abcd
bcde
cdef
defg
And then your break; hits.
Instead it should look something like this:
for ($i=0; $i<4; $i++) {
$matchup = array();
for ($j=0; $j<4; $j++) {
$matchup[] = $teams[4*$i+$j];
}
$schedule[$round][] = $matchup ;
}
This way you get the following pairing (again, using letters as teams):
abcd
efgh
ijkl
mnop
This algorithm will split the team list in four groups:
abcd|efgh|ijkl|mnop
Keep in mind that depending on the shuffling of the $teams array for the next round you might get the same opponent twice.
adei|klnf|gjmc|pobh
Here the teams ad, kl and op will face again.
I've searched through a number of similar questions, but unfortunately I haven't been able to find an answer to this problem. I hope someone can point me in the right direction.
I need to come up with a PHP function which will produce a random number within a set range and mean. The range, in my case, will always be 1 to 100. The mean could be anything within the range.
For example...
r = f(x)
where...
r = the resulting random number
x = the mean
...running this function in a loop should produce random values where the average of the resulting values should be very close to x. (The more times we loop the closer we get to x)
Running the function in a loop, assuming x = 10, should produce a curve similar to this:
+
+ +
+ +
+ +
+ +
Where the curve starts at 1, peeks at 10, and ends at 100.
Unfortunately, I'm not well versed in statistics. Perhaps someone can help me word this problem correctly to find a solution?
interesting question. I'll sum it up:
We need a funcion f(x)
f returns an integer
if we run f a million times the average of the integer is x(or very close at least)
I am sure there are several approaches, but this uses the binomial distribution: http://en.wikipedia.org/wiki/Binomial_distribution
Here is the code:
function f($x){
$min = 0;
$max = 100;
$curve = 1.1;
$mean = $x;
$precision = 5; //higher is more precise but slower
$dist = array();
$lastval = $precision;
$belowsize = $mean-$min;
$abovesize = $max-$mean;
$belowfactor = pow(pow($curve,50),1/$belowsize);
$left = 0;
for($i = $min; $i< $mean; $i++){
$dist[$i] = round($lastval*$belowfactor);
$lastval = $lastval*$belowfactor;
$left += $dist[$i];
}
$dist[$mean] = round($lastval*$belowfactor);
$abovefactor = pow($left,1/$abovesize);
for($i = $mean+1; $i <= $max; $i++){
$dist[$i] = round($left-$left/$abovefactor);
$left = $left/$abovefactor;
}
$map = array();
foreach ($dist as $int => $quantity) {
for ($x = 0; $x < $quantity; $x++) {
$map[] = $int;
}
}
shuffle($map);
return current($map);
}
You can test it out like this(worked for me):
$results = array();
for($i = 0;$i<100;$i++){
$results[] = f(20);
}
$average = array_sum($results) / count($results);
echo $average;
It gives a distribution curve that looks like this:
I'm not sure if I got what you mean, even if I didn't this is still a pretty neat snippet:
<?php
function array_avg($array) { // Returns the average (mean) of the numbers in an array
return array_sum($array)/count($array);
}
function randomFromMean($x, $min = 1, $max = 100, $leniency = 3) {
/*
$x The number that you want to get close to
$min The minimum number in the range
$max Self-explanatory
$leniency How far off of $x can the result be
*/
$res = [mt_rand($min,$max)];
while (true) {
$res_avg = array_avg($res);
if ($res_avg >= ($x - $leniency) && $res_avg <= ($x + $leniency)) {
return $res;
break;
}
else if ($res_avg > $x && $res_avg < $max) {
array_push($res,mt_rand($min, $x));
}
else if ($res_avg > $min && $res_avg < $x) {
array_push($res, mt_rand($x,$max));
}
}
}
$res = randomFromMean(22); // This function returns an array of random numbers that have a mean close to the first param.
?>
If you then var_dump($res), You get something like this:
array (size=4)
0 => int 18
1 => int 54
2 => int 22
3 => int 4
EDIT: Using a low value for $leniency (like 1 or 2) will result in huge arrays, since testing, I recommend a leniency of around 3.
There seems to be a error in sma calculation in the code below... can someone point out where..
/**
* Simple Moving Average (sma)
*
*A Moving Average is an indicator that shows the average value of a security's price over a period of time. When calculating a moving average, a mathematical analysis of the security's average value over a predetermined time period is made. As the security's price changes,its average price moves up or down.
*
*A simple, or arithmetic, moving average is calculated by adding the closing price of the security for a number of time periods (e.g., 12 days) and then dividing this total by the number of time periods. The result is the average price of the security over the time period. Simple moving averages give equal weight to each daily price.
*
*Formula:
*
* - SUM(Closes of n periods)/n
*/
<?php
$data = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);
class sma
{
/**
* #var double[]
*/
private $sma;
/**
* #param double[] $data
* #param int $range
* #return double[]
*/
function get( $data, $range )
{
$position = 0;
while ( empty( $data[ $position ] ) ) {
$position++;
}
$i = $position;
while ( true ) {
if ( empty( $data[ $i + $range - 1 ] ) ) {
break;
}
$temp_sum = 0;
for ( $j = $i; $j < $i + $range; $j++ ) {
$temp_sum += $data[ $j ];
}
$this->sma[ $i + $range - 1 ] = $temp_sum / $range;
$i++;
}
return $this->sma;
}
}
$mysma = new sma();
$mysma->get($data,5); $sma = $mysma->get();
echo mysma;
?>
Also sma calculation in other code seems to be easier.. a few example is here.. if someone has done it in php similarly..??
(defn moving-average
[coll n]
(cond
(< n 1) nil
(= n 1) coll
:else (let [sums (reductions + 0 coll)]
(map #(/ (- %1 %2) n) (drop n sums) sums))))
(time (doall (moving-average coll n)))
# "Elapsed time: 9.184 msecs"
Also this..
double[] MovingAverage(int period, double[] source)
{
var ma = new double[source.Length];
double sum = 0;
for (int bar = 0; bar < period; bar++)
sum += source[bar];
ma[period - 1] = sum/period;
for (int bar = period; bar < source.Length; bar++)
ma[bar] = ma[bar - 1] + source[bar]/period
- source[bar - period]/period;
return ma;
}
Here's a translation based on the last piece of code in your question:
function get(array $data, $range)
{
$sum = array_sum(array_slice($data, 0, $range));
$result = array($range - 1 => $sum / $range);
for ($i = $range, $n = count($data); $i != $n; ++$i) {
$result[$i] = $result[$i - 1] + ($data[$i] - $data[$i - $range]) / $range;
}
return $result;
}