ackb ha ragione nel dire che queste soluzioni basate su vettori non possono essere considerate vere medie di angoli, sono solo una media delle controparti vettoriali unitarie. Tuttavia, la soluzione suggerita di ackb non sembra suonare matematicamente.
Quella che segue è una soluzione che è matematicamente derivata dall'obiettivo di minimizzare (angolo [i] - avgAngle) ^ 2 (dove la differenza è corretta se necessario), che la rende una vera media aritmetica degli angoli.
Innanzitutto, dobbiamo guardare esattamente a quali casi la differenza tra gli angoli è diversa dalla differenza tra le loro controparti numeriche normali. Considera gli angoli xey, se y> = x - 180 e y <= x + 180, allora possiamo usare direttamente la differenza (xy). Altrimenti, se la prima condizione non è soddisfatta, dobbiamo usare (y + 360) nel calcolo invece di y. Di conseguenza, se la seconda condizione non è soddisfatta, dobbiamo usare (y-360) invece di y. Poiché l'equazione della curva stiamo minimizzando solo i cambiamenti nei punti in cui queste disuguaglianze cambiano da vero a falso o viceversa, possiamo separare l'intero intervallo [0,360) in un insieme di segmenti, separati da questi punti. Quindi, dobbiamo solo trovare il minimo di ciascuno di questi segmenti e quindi il minimo del minimo di ciascun segmento, che è la media.
Ecco un'immagine che dimostra dove si verificano i problemi nel calcolo delle differenze angolari. Se x si trova nell'area grigia, allora ci sarà un problema.
Per minimizzare una variabile, a seconda della curva, possiamo prendere la derivata di ciò che vogliamo minimizzare e quindi troviamo il punto di svolta (che è dove la derivata = 0).
Qui applicheremo l'idea di minimizzare la differenza quadrata per derivare la formula media aritmetica comune: sum (a [i]) / n. La curva y = sum ((a [i] -x) ^ 2) può essere minimizzata in questo modo:
y = sum((a[i]-x)^2)
= sum(a[i]^2 - 2*a[i]*x + x^2)
= sum(a[i]^2) - 2*x*sum(a[i]) + n*x^2
dy\dx = -2*sum(a[i]) + 2*n*x
for dy/dx = 0:
-2*sum(a[i]) + 2*n*x = 0
-> n*x = sum(a[i])
-> x = sum(a[i])/n
Ora applicandolo alle curve con le nostre differenze modificate:
b = sottoinsieme di a dove la differenza (angolare) corretta a [i] -xc = sottoinsieme di a dove la differenza (angolare) corretta (a [i] -360) -x cn = dimensione di cd = sottoinsieme di a dove il differenza (angolare) corretta (a [i] +360) -x dn = dimensione di d
y = sum((b[i]-x)^2) + sum(((c[i]-360)-b)^2) + sum(((d[i]+360)-c)^2)
= sum(b[i]^2 - 2*b[i]*x + x^2)
+ sum((c[i]-360)^2 - 2*(c[i]-360)*x + x^2)
+ sum((d[i]+360)^2 - 2*(d[i]+360)*x + x^2)
= sum(b[i]^2) - 2*x*sum(b[i])
+ sum((c[i]-360)^2) - 2*x*(sum(c[i]) - 360*cn)
+ sum((d[i]+360)^2) - 2*x*(sum(d[i]) + 360*dn)
+ n*x^2
= sum(b[i]^2) + sum((c[i]-360)^2) + sum((d[i]+360)^2)
- 2*x*(sum(b[i]) + sum(c[i]) + sum(d[i]))
- 2*x*(360*dn - 360*cn)
+ n*x^2
= sum(b[i]^2) + sum((c[i]-360)^2) + sum((d[i]+360)^2)
- 2*x*sum(x[i])
- 2*x*360*(dn - cn)
+ n*x^2
dy/dx = 2*n*x - 2*sum(x[i]) - 2*360*(dn - cn)
for dy/dx = 0:
2*n*x - 2*sum(x[i]) - 2*360*(dn - cn) = 0
n*x = sum(x[i]) + 360*(dn - cn)
x = (sum(x[i]) + 360*(dn - cn))/n
Questo da solo non è abbastanza per ottenere il minimo, mentre funziona per valori normali, che ha un set illimitato, quindi il risultato sarà sicuramente compreso nell'intervallo del set ed è quindi valido. Abbiamo bisogno del minimo entro un intervallo (definito dal segmento). Se il minimo è inferiore al limite inferiore del nostro segmento, allora il minimo di quel segmento deve essere al limite inferiore (poiché le curve quadratiche hanno solo 1 punto di svolta) e se il minimo è maggiore del limite superiore del nostro segmento, il minimo del segmento è al limite superiore. Dopo che abbiamo il minimo per ogni segmento, troviamo semplicemente quello che ha il valore più basso per ciò che stiamo minimizzando (sum ((b [i] -x) ^ 2) + sum ((((c [i] -360) ) -b) ^ 2) + sum (((d [i] +360) -c) ^ 2)).
Ecco un'immagine della curva, che mostra come cambia nei punti in cui x = (a [i] +180)% 360. Il set di dati in questione è {65,92.230.320.250}.
Ecco un'implementazione dell'algoritmo in Java, incluse alcune ottimizzazioni, la sua complessità è O (nlogn). Può essere ridotto a O (n) se si sostituisce l'ordinamento basato sul confronto con un ordinamento non basato sul confronto, come ad esempio l'ordinamento radix.
static double varnc(double _mean, int _n, double _sumX, double _sumSqrX)
{
return _mean*(_n*_mean - 2*_sumX) + _sumSqrX;
}
//with lower correction
static double varlc(double _mean, int _n, double _sumX, double _sumSqrX, int _nc, double _sumC)
{
return _mean*(_n*_mean - 2*_sumX) + _sumSqrX
+ 2*360*_sumC + _nc*(-2*360*_mean + 360*360);
}
//with upper correction
static double varuc(double _mean, int _n, double _sumX, double _sumSqrX, int _nc, double _sumC)
{
return _mean*(_n*_mean - 2*_sumX) + _sumSqrX
- 2*360*_sumC + _nc*(2*360*_mean + 360*360);
}
static double[] averageAngles(double[] _angles)
{
double sumAngles;
double sumSqrAngles;
double[] lowerAngles;
double[] upperAngles;
{
List<Double> lowerAngles_ = new LinkedList<Double>();
List<Double> upperAngles_ = new LinkedList<Double>();
sumAngles = 0;
sumSqrAngles = 0;
for(double angle : _angles)
{
sumAngles += angle;
sumSqrAngles += angle*angle;
if(angle < 180)
lowerAngles_.add(angle);
else if(angle > 180)
upperAngles_.add(angle);
}
Collections.sort(lowerAngles_);
Collections.sort(upperAngles_,Collections.reverseOrder());
lowerAngles = new double[lowerAngles_.size()];
Iterator<Double> lowerAnglesIter = lowerAngles_.iterator();
for(int i = 0; i < lowerAngles_.size(); i++)
lowerAngles[i] = lowerAnglesIter.next();
upperAngles = new double[upperAngles_.size()];
Iterator<Double> upperAnglesIter = upperAngles_.iterator();
for(int i = 0; i < upperAngles_.size(); i++)
upperAngles[i] = upperAnglesIter.next();
}
List<Double> averageAngles = new LinkedList<Double>();
averageAngles.add(180d);
double variance = varnc(180,_angles.length,sumAngles,sumSqrAngles);
double lowerBound = 180;
double sumLC = 0;
for(int i = 0; i < lowerAngles.length; i++)
{
//get average for a segment based on minimum
double testAverageAngle = (sumAngles + 360*i)/_angles.length;
//minimum is outside segment range (therefore not directly relevant)
//since it is greater than lowerAngles[i], the minimum for the segment
//must lie on the boundary lowerAngles[i]
if(testAverageAngle > lowerAngles[i]+180)
testAverageAngle = lowerAngles[i];
if(testAverageAngle > lowerBound)
{
double testVariance = varlc(testAverageAngle,_angles.length,sumAngles,sumSqrAngles,i,sumLC);
if(testVariance < variance)
{
averageAngles.clear();
averageAngles.add(testAverageAngle);
variance = testVariance;
}
else if(testVariance == variance)
averageAngles.add(testAverageAngle);
}
lowerBound = lowerAngles[i];
sumLC += lowerAngles[i];
}
//Test last segment
{
//get average for a segment based on minimum
double testAverageAngle = (sumAngles + 360*lowerAngles.length)/_angles.length;
//minimum is inside segment range
//we will test average 0 (360) later
if(testAverageAngle < 360 && testAverageAngle > lowerBound)
{
double testVariance = varlc(testAverageAngle,_angles.length,sumAngles,sumSqrAngles,lowerAngles.length,sumLC);
if(testVariance < variance)
{
averageAngles.clear();
averageAngles.add(testAverageAngle);
variance = testVariance;
}
else if(testVariance == variance)
averageAngles.add(testAverageAngle);
}
}
double upperBound = 180;
double sumUC = 0;
for(int i = 0; i < upperAngles.length; i++)
{
//get average for a segment based on minimum
double testAverageAngle = (sumAngles - 360*i)/_angles.length;
//minimum is outside segment range (therefore not directly relevant)
//since it is greater than lowerAngles[i], the minimum for the segment
//must lie on the boundary lowerAngles[i]
if(testAverageAngle < upperAngles[i]-180)
testAverageAngle = upperAngles[i];
if(testAverageAngle < upperBound)
{
double testVariance = varuc(testAverageAngle,_angles.length,sumAngles,sumSqrAngles,i,sumUC);
if(testVariance < variance)
{
averageAngles.clear();
averageAngles.add(testAverageAngle);
variance = testVariance;
}
else if(testVariance == variance)
averageAngles.add(testAverageAngle);
}
upperBound = upperAngles[i];
sumUC += upperBound;
}
//Test last segment
{
//get average for a segment based on minimum
double testAverageAngle = (sumAngles - 360*upperAngles.length)/_angles.length;
//minimum is inside segment range
//we test average 0 (360) now
if(testAverageAngle < 0)
testAverageAngle = 0;
if(testAverageAngle < upperBound)
{
double testVariance = varuc(testAverageAngle,_angles.length,sumAngles,sumSqrAngles,upperAngles.length,sumUC);
if(testVariance < variance)
{
averageAngles.clear();
averageAngles.add(testAverageAngle);
variance = testVariance;
}
else if(testVariance == variance)
averageAngles.add(testAverageAngle);
}
}
double[] averageAngles_ = new double[averageAngles.size()];
Iterator<Double> averageAnglesIter = averageAngles.iterator();
for(int i = 0; i < averageAngles_.length; i++)
averageAngles_[i] = averageAnglesIter.next();
return averageAngles_;
}
La media aritmetica di una serie di angoli potrebbe non essere d'accordo con la tua idea intuitiva di quale dovrebbe essere la media. Ad esempio, la media aritmetica dell'insieme {179.179,0.181.181} è 216 (e 144). La risposta che pensi immediatamente è probabilmente 180, tuttavia è risaputo che la media aritmetica è fortemente influenzata dai valori dei bordi. Dovresti anche ricordare che gli angoli non sono vettori, tanto attraenti come può sembrare quando si affrontano gli angoli a volte.
Questo algoritmo si applica ovviamente anche a tutte le quantità che obbediscono all'aritmetica modulare (con aggiustamento minimo), come l'ora del giorno.
Vorrei anche sottolineare che anche se questa è una vera media di angoli, a differenza delle soluzioni vettoriali, ciò non significa necessariamente che sia la soluzione che dovresti usare, la media dei corrispondenti vettori di unità potrebbe essere il valore che in realtà dovrebbe usare.