0

I would like to compute the average or sum or another method over elements in an array of values(doubles) conditional on a second array with same length.

For instance, I have a sorted array with dates (double[]dts) and now I would like to compute the average between start date (std) and end date (edd) over the value array (double[]vals) (which is simple and what my code below does).

But if I want to compute e.g. the average of all Mondays in a year the code below doesnt work any more. Any ideas? Thanks!

public static double aggr(double[] dts, double[] vals, double std, double edd)
    {
        int stdindex = 0;
        int eddindex = dts.Length;

        for (int k = 0; k < dts.Length; k++)
        {
            if (dts[k] == std)
            {
                stdindex = k;
            }
            if (dts[k] == edd)
            {
                eddindex = k;
            }
        }


        return vals.Skip(stdindex).Take(eddindex-stdindex).Average();
    }

THANKS everybody. I think all answers work. Here is my new code:

 public static double aggr(double[] dts, double[] vals, double std, double edd, string peakoffpeakbase, double sumavg)
    {
        double result;
        if (sumavg == 1)
        {

            if (peakoffpeakbase=="Peak")
            {
                    result = dts.Zip(vals, (d, v) => new { d, v })
                                        .Where(dv =>  (dv.d >= std & dv.d < edd & DateTime.FromOADate(dv.d).DayOfWeek != DayOfWeek.Saturday & DateTime.FromOADate(dv.d).DayOfWeek != DayOfWeek.Sunday & DateTime.FromOADate(dv.d).Hour > 7 & DateTime.FromOADate(dv.d).Hour < 20))
                                        .Select(dv => dv.v).Sum();
            }
             else if (peakoffpeakbase=="Offpeak")
            {
                    result = dts.Zip(vals, (d, v) => new { d, v })
                                        .Where(dv => (dv.d >= std & dv.d < edd & DateTime.FromOADate(dv.d).DayOfWeek == DayOfWeek.Saturday | DateTime.FromOADate(dv.d).DayOfWeek == DayOfWeek.Sunday | DateTime.FromOADate(dv.d).Hour < 8 | DateTime.FromOADate(dv.d).Hour > 19))
                                        .Select(dv => dv.v).Sum();
             }
             else
             {
                    result = dts.Zip(vals, (d, v) => new { d, v })
                                       .Where(dv => (dv.d >= std && dv.d < edd))
                                       .Select(dv => dv.v).Sum();
            }
        }

        else
        {

            if (peakoffpeakbase == "Peak")
            {
                result = dts.Zip(vals, (d, v) => new { d, v })
                                    .Where(dv => (dv.d >= std & dv.d < edd & DateTime.FromOADate(dv.d).DayOfWeek != DayOfWeek.Saturday & DateTime.FromOADate(dv.d).DayOfWeek != DayOfWeek.Sunday & DateTime.FromOADate(dv.d).Hour > 7 & DateTime.FromOADate(dv.d).Hour < 20))
                                    .Select(dv => dv.v).Average();
            }
            else if (peakoffpeakbase == "Offpeak")
            {
                result = dts.Zip(vals, (d, v) => new { d, v })
                                    .Where(dv => (dv.d >= std & dv.d < edd & (DateTime.FromOADate(dv.d).DayOfWeek == DayOfWeek.Saturday | DateTime.FromOADate(dv.d).DayOfWeek == DayOfWeek.Sunday | DateTime.FromOADate(dv.d).Hour < 8 | DateTime.FromOADate(dv.d).Hour > 19)))
                                    .Select(dv => dv.v).Average();
            }
            else
            {
                result = dts.Zip(vals, (d, v) => new { d, v })
                                   .Where(dv => (dv.d >= std && dv.d < edd))
                                   .Select(dv => dv.v).Average();
            }
        }
            return result;

    }

Obviously this is terrible and very verbose. What I would really like to do is combine the answers below and write sth like:

result = dts.Zip(vals, (d, v) => new { d, v })
                                        .Where(dv =>  (dv.d.InTimeRange(std,edd).IsBusinessHour(peakoffpeakbase))
                                        .Select(dv => dv.v).CustomX(indicator);

Where InTimeRange and IsBusinessHour are extension methods as described below and customX would take an argument and then either average, sum or do sth else. However I cant get that to work. Thanks again!

5
  • so....what are you looking for? Commented Apr 5, 2013 at 13:59
  • upload.wikimedia.org/math/7/e/e/… You can not use arithmetic everage to date time. Because you can not add DateTime to DateTime and you can't divide DateTime by n. Commented Apr 5, 2013 at 14:01
  • What do you whant to obtains as a result of Average method? Commented Apr 5, 2013 at 14:02
  • Please provide a bit of example data - what do the arrays look like? What do you want your results to look like? Commented Apr 5, 2013 at 14:14
  • I would like to average over the values of array vals but only if the date in array dts (in the same index position) meets a certain criteria. For instance, if array vals contains the share price for each day in a year and dts the corresponding date I would like to get the avarage share price on all mondays. I hope that is clearer. Thanks. Commented Apr 5, 2013 at 14:18

3 Answers 3

2

You can use Zip to combine the two arrays:

double result = dts.Zip(vals, (d,v) => new { d, v})
    .Where( dv => SomeCondition(dv.d))
    .Select( dv => dv.v).Average();

This calculates the average of all vals values, for which the predicate SomeCondition returns true for the corresponding dts value.

Sign up to request clarification or add additional context in comments.

Comments

0

I am not entirely sure what you are looking for but have you tried converting the double to a DateTime so you can check against it?

DateTime.FromOADate(dts[k]).DayOfWeek

Maybe something like this:

public static double aggr(double[] dts, double[] vals, double std, double edd, DayOfWeek dayOfWeek)
    {
         int stdindex = 0;
        int eddindex = dts.Length;

        List<double> newDts = new List<double>();
        List<double> newVals = new List<double>();

        // Use only values that meet criteria
        for (int k = 0; k < dts.Length; k++)
        {
            if (DateTime.FromOADate(dts[k]).DayOfWeek == dayOfWeek)
            {
                newDts.Add(dts[k]);
                newVals.Add(vals[k]);
            }
        }

        // Loop through dates that met the criteria
        for (int k = 0; k < newDts.Count; k++)
        {
            if (newDts[k] == std)
            {
                stdindex = k;
            }
            if (newDts[k] == edd)
            {
                eddindex = k;
            }
        }

        return newVals.Skip(stdindex).Take(eddindex - stdindex).Average();
    }

Comments

0

The answer above (Henrik's) is the shortest one, but if you find you need to use specific filter ranges fairly often you can write extension methods to make it more readable.

You could convert your data to a more easily used form. For example, you could convert the double dates to DateTime. Then you could put the data into a collection of these:

public class Datum
{
    public DateTime DateTime;
    public double Value;
}

That will simplify the date checking.

Then you can implement your range selection as two orthogonal concepts:

  1. The notion of a start and end date.
  2. The notion of filtering by a particular day of the week.

You can implement these filters - and the calculation of the average too - as extension methods on IEnumerable like so:

public static class EnumerableDatumExt
{
    public static double Average(this IEnumerable<Datum> @this)
    {
        return @this.Average(datum => datum.Value);
    }

    public static IEnumerable<Datum> InTimeRange(this IEnumerable<Datum> @this, DateTime start, DateTime end)
    {
        return from datum in @this
               where (start <= datum.DateTime && datum.DateTime <= end)
               select datum;
    }

    public static IEnumerable<Datum> ForEach(this IEnumerable<Datum> @this, DayOfWeek targetDayOfWeek)
    {
        return from datum in @this
               where datum.DateTime.DayOfWeek == targetDayOfWeek
               select datum;
    }
}

Then you can compose the filters to give you the average for all the data within a specified time range for a particular day of the week like this:

double average = data.ForEach(DayOfWeek.Monday).InTimeRange(start, end).Average();
// Or
double average = data.InTimeRange(start, end).ForEach(DayOfWeek.Monday).Average();

Putting this all together into a compilable Console app:

using System;
using System.Linq;
using System.Collections.Generic;

namespace Demo
{
    public class Datum
    {
        public DateTime DateTime;
        public double Value;
    }

    public static class EnumerableDatumExt
    {
        public static double Average(this IEnumerable<Datum> @this)
        {
            return @this.Average(datum => datum.Value);
        }

        public static IEnumerable<Datum> InTimeRange(this IEnumerable<Datum> @this, DateTime start, DateTime end)
        {
            return from datum in @this
                   where (start <= datum.DateTime && datum.DateTime <= end)
                   select datum;
        }

        public static IEnumerable<Datum> ForEach(this IEnumerable<Datum> @this, DayOfWeek targetDayOfWeek)
        {
            return from datum in @this
                   where datum.DateTime.DayOfWeek == targetDayOfWeek
                   select datum;
        }
    }

    static class Program
    {
        static void Main()
        {
            var start  = new DateTime(2012, 01, 01);
            var end    = new DateTime(2012, 01, 31);
            var data   = createTestData(start);

            double average = data.ForEach(DayOfWeek.Monday).InTimeRange(start, end).Average();

            Console.WriteLine(average);
        }

        private static IEnumerable<Datum> createTestData(DateTime start)
        {
            var result = new List<Datum>();
            var oneDay = TimeSpan.FromDays(1);

            for (int i = 0; i < 20; ++i)
            {
                start += oneDay;
                result.Add(new Datum {DateTime = start, Value = i});
            }

            return result;
        }
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.