function countBy( collection, options, indicator ) {
	var thisArg;
	var opts;
	var err;
	var out;
	var len;
	var cb;
	var g;
	var i;
	if ( !isCollection( collection ) ) {
		throw new TypeError( format( 'invalid argument. First argument must be a collection. Value: `%s`.', collection ) );
	}
	opts = {};
	if ( arguments.length === 2 ) {
		cb = options;
	} else {
		err = validate( opts, options );
		if ( err ) {
			throw err;
		}
		cb = indicator;
	}
	if ( !isFunction( cb ) ) {
		throw new TypeError( format( 'invalid argument. Last argument must be a function. Value: `%s`.', cb ) );
	}
	thisArg = opts.thisArg;
	len = collection.length;
	out = {};
	for ( i = 0; i < len; i++ ) {
		g = cb.call( thisArg, collection[ i ], i );
		if ( hasOwnProp( out, g ) ) {
			out[ g ] += 1;
		} else {
			out[ g ] = 1;
		}
	}
	return out;
}
Groups values according to an indicator function and returns group counts.

@param {Collection} collection - input collection
@param {Options} [options] - function options
@param {*} [options.thisArg] - execution context
@param {Function} indicator - indicator function specifying which group an element in the input collection belongs to
@throws {TypeError} first argument must be a collection
@throws {TypeError} options argument must be an object
@throws {TypeError} last argument must be a function
@throws {TypeError} must provide valid options
@returns {Object} counts

@example
function indicator( v ) {
    return v[ 0 ];
}
var arr = [ 'beep', 'boop', 'foo', 'bar' ];

var out = countBy( arr, indicator );
// returns { 'b': 3, 'f': 1 }