A ackb está certa de que essas soluções baseadas em vetores não podem ser consideradas médias reais dos ângulos, elas são apenas uma média das contrapartes dos vetores unitários. No entanto, a solução sugerida pelo ackb não parece matematicamente correta.
A seguir, é apresentada uma solução matematicamente derivada do objetivo de minimizar (ângulo [i] - ângulo médio) ^ 2 (onde a diferença é corrigida, se necessário), o que a torna uma verdadeira média aritmética dos ângulos.
Primeiro, precisamos examinar exatamente em quais casos a diferença entre os ângulos é diferente da diferença entre os números normais. Considere os ângulos x e y, se y> = x - 180 e y <= x + 180, então podemos usar a diferença (xy) diretamente. Caso contrário, se a primeira condição não for atendida, devemos usar (y + 360) no cálculo em vez de y. Correspondente, se a segunda condição não for atendida, devemos usar (y-360) em vez de y. Como a equação da curva estamos minimizando apenas as mudanças nos pontos em que essas desigualdades mudam de verdadeiro para falso ou vice-versa, podemos separar o intervalo [0,360) completo em um conjunto de segmentos, separados por esses pontos. Então, precisamos encontrar apenas o mínimo de cada um desses segmentos e, em seguida, o mínimo do mínimo de cada segmento, que é a média.
Aqui está uma imagem demonstrando onde os problemas ocorrem no cálculo das diferenças de ângulo. Se x estiver na área cinza, haverá um problema.
Para minimizar uma variável, dependendo da curva, podemos tomar a derivada do que queremos minimizar e depois encontrar o ponto de virada (que é onde a derivada = 0).
Aqui aplicaremos a idéia de minimizar a diferença ao quadrado para derivar a fórmula da média aritmética comum: sum (a [i]) / n. A curva y = soma ((a [i] -x) ^ 2) pode ser minimizada desta maneira:
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
Agora, aplicando-o a curvas com nossas diferenças ajustadas:
b = subconjunto de a onde a diferença (angular) correta a [i] -xc = subconjunto de a onde a diferença (angular) correta (a [i] -360) -x cn = tamanho do cd = subconjunto de a onde o diferença (angular) correta (a [i] +360) -x dn = tamanho de 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
Isso por si só não é suficiente para obter o mínimo, enquanto trabalha para valores normais, que tem um conjunto ilimitado, portanto o resultado definitivamente ficará dentro do intervalo do conjunto e, portanto, é válido. Precisamos do mínimo dentro de um intervalo (definido pelo segmento). Se o mínimo for menor que o limite inferior do nosso segmento, o mínimo desse segmento deverá estar no limite inferior (porque as curvas quadráticas têm apenas 1 ponto de viragem) e se o mínimo for maior que o limite superior do segmento, o mínimo do segmento estará no limite superior. Depois de termos o mínimo para cada segmento, simplesmente encontramos o que tem o menor valor para o que estamos minimizando (soma ((b [i] -x) ^ 2) + soma (((c [i] -360 ) -b) ^ 2) + soma (((d [i] +360) -c) ^ 2)).
Aqui está uma imagem da curva, que mostra como ela muda nos pontos em que x = (a [i] +180)% 360. O conjunto de dados em questão é {65,92,230,320,250}.
Aqui está uma implementação do algoritmo em Java, incluindo algumas otimizações, sua complexidade é O (nlogn). Ele pode ser reduzido para O (n) se você substituir a classificação baseada em comparação por uma classificação não baseada em comparação, como a classificação 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_;
}
A média aritmética de um conjunto de ângulos pode não concordar com sua ideia intuitiva de qual deve ser a média. Por exemplo, a média aritmética do conjunto {179,179,0,181,181} é 216 (e 144). A resposta em que você pensa imediatamente é provavelmente 180, no entanto, é sabido que a média aritmética é fortemente afetada pelos valores das arestas. Lembre-se também de que ângulos não são vetores, por mais atraente que possa parecer ao lidar com ângulos algumas vezes.
É claro que esse algoritmo também se aplica a todas as quantidades que obedecem à aritmética modular (com ajuste mínimo), como a hora do dia.
Eu também gostaria de enfatizar que, embora essa seja uma média real de ângulos, diferentemente das soluções vetoriais, isso não significa necessariamente que é a solução que você deve usar, a média dos vetores unitários correspondentes pode muito bem ser o valor que você realmente deveria estar usando.