blob: bab657d0c7414cf7ba8c4680a297465e58819a72 [file]
// Copyright (c) 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be found
// in the LICENSE file.
//
// ctrace makes Traces into ClusterableTraces which can then be used in kmeans.
package ctrace
import (
"fmt"
"math"
)
import (
"config"
"kmeans"
)
const (
// MIN_STDDEV is the smallest standard deviation we will normalize, smaller
// than this and we presume it's a standard deviation of zero.
MIN_STDDEV = 0.01
)
// ClusterableTrace contains Trace data and implements kmeans.Clusterable.
type ClusterableTrace struct {
Key string
Values []float64
Params map[string]string
}
func (t *ClusterableTrace) Distance(other kmeans.Clusterable) float64 {
// Data is always loaded from BigQuery so that every Trace has the same length,
// and NewFullTrace keeps that guarantee.
o := other.(*ClusterableTrace)
sum := 0.0
for i, x := range t.Values {
sum += (x - o.Values[i]) * (x - o.Values[i])
}
return math.Sqrt(sum)
}
func (t *ClusterableTrace) String() string {
return fmt.Sprintf("%s %#v", t.Key, t.Values[:2])
}
// NewFullTrace takes data you would find in a Trace and returns a
// ClusterableTrace usable for kmeans clustering.
func NewFullTrace(key string, values []float64, params map[string]string, minStdDev float64) *ClusterableTrace {
norm := make([]float64, len(values))
// Find the first non-sentinel data point.
last := 0.0
for _, x := range values {
if x != config.MISSING_DATA_SENTINEL {
last = x
break
}
}
// Copy over the data from values, backfilling in sentinels with
// older points, except for the beginning of the array where
// we can't do that, so we fill those points in using the first
// non sentinel.
// So
// [1e100, 1e100, 2, 3, 1e100, 5]
// becomes
// [2 , 2 , 2, 3, 3 , 5]
//
sum := 0.0
sum2 := 0.0
for i, x := range values {
if x == config.MISSING_DATA_SENTINEL {
norm[i] = last
} else {
norm[i] = x
last = x
}
sum += norm[i]
sum2 += norm[i] * norm[i]
}
mean := sum / float64(len(norm))
stddev := math.Sqrt(sum2/float64(len(norm)) - mean*mean)
// Normalize the data to a mean of 0 and standard deviation of 1.0.
for i, _ := range norm {
norm[i] -= mean
if stddev > MIN_STDDEV {
norm[i] = norm[i] / stddev
}
}
return &ClusterableTrace{
Key: key,
Values: norm,
Params: params,
}
}
// CalculateCentroid implements kmeans.CalculateCentroid.
func CalculateCentroid(members []kmeans.Clusterable) kmeans.Clusterable {
first := members[0].(*ClusterableTrace)
mean := make([]float64, len(first.Values))
for _, m := range members {
ft := m.(*ClusterableTrace)
for i, x := range ft.Values {
mean[i] += x
}
}
numMembers := float64(len(members))
for i, _ := range mean {
mean[i] = mean[i] / numMembers
}
return &ClusterableTrace{
Key: "I'm a centroid!",
Values: mean,
}
}