plot_date problem

Thanks for the prompt response and the detailed explanation.

The scaling is working now, and I have not encountered those old problems. There are a couple of things I should mention however:

1) The axes.auto_scale() seems to be broken. I get a following error:

···

------------
Traceback (most recent call last):
File "./e3.py", line 97, in ?
   ax.autoscale_view()
File "/usr/lib/python2.2/site-packages/matplotlib/axes.py", line 436, in autoscale_view
   tup = locator.autoscale()
File "/usr/lib/python2.2/site-packages/matplotlib/ticker.py", line 327, in autoscale
   return self.nonsingular(self.dataInterval.get_bounds())
TypeError: nonsingular() takes exactly 3 arguments (2 given)
-------------

When I run this:

--------
#!/usr/bin/env python

import time
from matplotlib.dates import EpochConverter
from matplotlib.matlab import *
from matplotlib.ticker import FuncFormatter, NullLocator, MinuteLocator, DayLocator, HourLocator, MultipleLocator, DateFormatter

wantLegend=1

time1=[1087192789.89]
data1=[-65.54]

time2=[
1087161589.89 ,
1087192289.0,
1087192389.0,
1087192489.0,
1087192589.0,
1087192689.0,
1087192789.89 ,
1087192889.0,
1087192989.0,
1087193089.0,
1087193189.0,
1087193289.0,
1087238100.0 ,
]
data2=[
-55.44
-64.54 ,
-66.54 ,
-61.54 ,
-69.54 ,
-45.66,
-55.54 ,
-77.54,
-65.54 ,
-49.54 ,
-57.54 ,
-68.54 ,
-55.54 ,
-23.44
]

ax = subplot(111)

p1Size=len(time1)
p2Size=len(time2)

p1=plot_date(time1, data1, None, '-', color='r')
p2=plot_date(time2, data2, None, '-', color='b')

p1=plot(time1, data1,'-', color='r')
p2=plot(time2, data2,'-', color='b')

now=time2[-1]
then=time2[0]

deltaSec=now-then
deltaTickSec=deltaSec/7.0

tickList=[item for item in list(arange(then, now, deltaTickSec))]

def tickString(x, pos):
   return time.strftime("%H:%M:%S", time.localtime(x))

formatter = FuncFormatter(tickString)

ax.set_xticks(tickList)

ax.xaxis.set_major_formatter(formatter)
ax.xaxis.set_minor_locator(NullLocator())

#This line does not work.
ax.autoscale_view()

if wantLegend:
   legend((p1, p2), ('small data set (%d)' % p1Size, 'large data set (%d)' % p2Size))

xlabel('time')
grid(True)

show()
#savefig('./blah.png')

------

Things work when I comment out the line:

ax.autoscale_view()

2) The auto-scaling in plot_date() does not scale properly in some special cases. Consider this:

-----------------
from matplotlib.matlab import *

time2= [
1087321489.89, 1087321500.0,
1087321789.89, 1087321800.0, 1087322089.89, 1087322100.0, 1087322389.89, 1087322700.0, 1087322989.89,
1087323000.0, 1087323289.89, 1087323300.0, 1087323589.89, 1087323600.0, 1087323889.89, 1087323900.0,
1087324189.89, 1087324200.0, 1087324489.89, 1087324500.0, ]
  data2=[ 3.02,
       3.02,
       3.14,
       3.14,
       3.21,
       3.21,
       3.26,
       3.26,
       3.39,
       3.39,
       3.51,
       3.51,
       3.58,
       3.58,
       3.75,
       3.75,
       4.0,
       4.0,
       4.22,
       4.22,]

plot_date(time2, data2, None, '-', color='b')
xlabel('time')
grid(True)

show()
---------------

The same thing happens over differnt ranges when the amount of ticks is large. Perhaps you may use something similar to the code below
(from axes.py) to deal with these things. Note the ceilings get rid of the AssertErrors in ticks.Base when int() gives zero. Also, to finalize this,
one would have to write a DayMultiLocator type class for the Weeks, otherwise when the number of weeks is close, but less then the number
of weeks in numticks*months it will get crowded. This will probably be a little more involved than dealing with days, but perhaps one could use
your existent WeekdayLocator class to simplify the problem.

Also note that there was an error in defining months in your code; I changed the line to:

months = span/(SEC_PER_DAY*31) # approx

-----------
   def plot_date(self, d, y, converter, fmt='bo', **kwargs):
       """
       plot_date(d, y, converter, fmt='bo', **kwargs)

       d is a sequence of dates; converter is a dates.DateConverter
       instance that converts your dates to seconds since the epoch for
       plotting. y are the y values at those dates. fmt is a plot
       format string. kwargs are passed on to plot. See plot for more
       information.

       pass converter = None if your dates are already in epoch format
       """

       if not self._hold: self.cla()

       if converter is not None:
           e = array([converter.epoch(thisd) for thisd in d])
       else:
           e = d
                 assert(len(e))
       ret = self.plot(e, y, fmt, **kwargs)

       span = self.dataLim.intervalx().span()

       if span==0: span = SEC_PER_MIN
       minutes = span/SEC_PER_MIN
       hours = span/SEC_PER_HOUR
       days = span/SEC_PER_DAY
       weeks = span/SEC_PER_WEEK
       months = span/(SEC_PER_DAY*31) # approx years = span/(SEC_PER_WEEK*52) # approx

       #These should go to the top module.
       from math import ceil
       from ticker import DayMultiLocator
             numticks = 5
       if years>numticks:
           locator = YearLocator(ceil(years/numticks)) # define
           fmt = '%Y'
       elif months>numticks:
           locator = MonthLocator(ceil(months/numticks)) # define
           fmt = '%b %Y'
                    elif weeks>numticks:
           locator = WeekdayLocator(0)
           fmt = '%b %d'

       #Hack - need a DayLocator which operates like HourLocator
       #ie. Have a tick every so many days other than every day at a
       #particular hour.
       #This is a class I added to ticker.py:
## class DayMultiLocator(MultipleLocator):
## """
## Make ticks on day which are multiples of base
## """
## def __init__(self, base):
## MultipleLocator.__init__(self, base*SEC_PER_DAY)

       elif days>numticks:
           locator = DayMultiLocator(ceil(days/numticks))
           fmt = '%b %d'
       elif hours>numticks:
           locator = HourLocator(ceil(hours/numticks))
           fmt = '%H:%M\n%b %d'
       elif minutes>numticks:
           locator = MinuteLocator(ceil(minutes/numticks))
           fmt = '%H:%M:%S'
       else:
           locator = MinuteLocator(1)
           fmt = '%H:%M:%S'

       formatter = DateFormatter(fmt)
       self.xaxis.set_major_locator(locator)
       self.xaxis.set_major_formatter(formatter)
       self.autoscale_view()
       return ret

Let me know what you think.

Peter

John Hunter wrote:

   > It does?! Do you mean that this is done automatically? Can
   > you show me an example of this using only the time module
   > (since I use python2.2, dont have datetime)? I thought that
   > I had to manually set things up and tell matplotlib whether

No, it's designed to work automagically. If you take a look at
axes.Axes.plot_date you'll see how it works. It looks at the max/min
span of your data and chooses a locator and formatter accordingly.
All the examples use custom locators and formatters ( I was busy
showing off how you could customize), but you don't need to

from matplotlib.matlab import *

time2= [ 1087161589.89 , 1087192289.0, 1087192389.0, 1087192489.0,
        1087192589.0, 1087192689.0, 1087192789.89 , 1087192889.0,
        1087192989.0, 1087193089.0, 1087193189.0, 1087193289.0, 1087238100.0,
        ]
   data2=[ -55.44 -64.54 , -66.54 , -61.54 , -69.54 , -45.66, -55.54,
       -77.54, -65.54 , -49.54 , -57.54 , -68.54 , -55.54 , -23.44 ]

plot_date(time2, data2, None, '-', color='b')
xlabel('time')
grid(True)

show()

In general you should try not to set the limits or ticks explicitly
since this is the job of the autolocator. I realize you were
encountering troubles and this is what led you to do it. Hopefully
the discussion below will save you from having to do this.

   > On the other note, regarding the weird scaling that I
   > talked about (and showed pretty pics for) in my last mail,
   > I finally put together a small script that exposes the
   > problem. It is a bit rough because I ripped bits and pieces
   > from here and there, but shows the issue. Use the
   > 'wantBadPlot' and 'wantStandardDateTics' to see how things
   > go wrong.

I looked at this and figured out the root cause of the problem. Your
"bad data" is a singleton plot

time1=[1087192789.89]
data1=[-65.54]
p1=plot_date(time1, data1, None, '-', color='r')

These are difficult to deal with - I'll explain why. The default
linear transformation maps data limits to display limits, and needs to
divide by the data span to do so. In this case min=max so the span is
zero and the transformation is undefined. To handle this case, I
detect the span=0 case and reset the data limits, lowering the min a
bit and raising the max a bit.
In earlier versions of maptlotlib, I did

           minx, maxx = min(xdata), max(xdata)
           if minx==maxx:
               minx -= 1
               maxx += 1

A problem arose if a person issued multiple calls to plot, first with
a singleton and then with a range of data, all really small

>>> plot([1e-9], [10]) # x limits are now approx -1, 1 due to above
>>> plot([1e-9,2e-9,3e-9], [10,20,30])
after the second call the data limits are still -1,1 because the limit
code only changes the data limits if the new lines are outside the
range of the existing limits. In this case they are not. The effect
is the plot is badly scaled since the data limits are wide compared to
all the xdata.

Thinking myself clever (first mistake!), I rewrote the above code as

           minx, maxx = min(xdata), max(xdata)
           if minx==maxx:
               minx -= 0.001*abs(minx)
               maxx += 0.001*abs(maxx)

So the scaling would be proportionate to the data size. This helps in
some cases, but is failing for you. Again, in your example code, you
do the dreaded singleton plot followed by more plot commands

   p1=plot_date([1087192789.89], [-65.54], None, '-', color='r')
   p2=plot_date(times, values, None, '-', color='r')

Since minx==maxx in the first plot, the xlimits become 1086105597.1,
1088279982.67. This is very wide compared to the subsequent range of
data you put in in the second plot_date call. This explains the bad
scaling you are seeing.

Upon writing this, I realized that the fundamental problem is that I
am manipulating data limits to deal with a singular range, when the
data limits should *always* accurately reflect the true data limits
(doh!). It's the view limits that need to be mucked with to handle
this case. I had to make a number of changes throughout the code to
get everybody playing nicely with this new insight, but now everything
is working. Even your wantBadPlot example!

Give it a try:

http://nitace.bsd.uchicago.edu:8080/files/share/matplotlib-0.54.3a.tar.gz

and let me know if it passes your tests.

JDH

---- Msg sent via @Mail - http://WebBasedEmail.com/

Hi:

Below is a little part of my post from a while ago regarding x-axis scaling on plot_date plots.

2) The auto-scaling in plot_date() does not scale properly in some special cases. Consider this:

-----------------
from matplotlib.matlab import *

time2= [
1087321489.89, 1087321500.0,
1087321789.89, 1087321800.0, 1087322089.89, 1087322100.0, 1087322389.89, 1087322700.0, 1087322989.89,
1087323000.0, 1087323289.89, 1087323300.0, 1087323589.89, 1087323600.0, 1087323889.89, 1087323900.0,
1087324189.89, 1087324200.0, 1087324489.89, 1087324500.0, ]
data2=[ 3.02,
3.02,
3.14,
3.21,
3.26,
3.39,
3.51,
3.58,
3.75,
4.0,
4.22,
4.22,]

plot_date(time2, data2, None, '-', color='b')
xlabel('time')
grid(True)

show()
---------------

The same thing happens over differnt ranges when the amount of ticks is large. Perhaps you may use something similar to the code below
(from axes.py) to deal with these things. Note the ceilings get rid of the AssertErrors in ticks.Base when int() gives zero. Also, to finalize this,
one would have to write a DayMultiLocator type class for the Weeks, otherwise when the number of weeks is close, but less then the number
of weeks in numticks*months it will get crowded. This will probably be a little more involved than dealing with days, but perhaps one could use
your existent WeekdayLocator class to simplify the problem.

I added a quick and dirty version of this WeekMultiLocator that handles cases when the time range is many weeks but less than 5 months (say 17 weeks) and the ticks get over-crowded. Ideally one could have the weeks always start on some particular day - say Monday, but for me it doesnt really matter, and with the simple code below, thing seem to come out quite nice.

In axes.py need:

Line ~19:

from ticker import YearLocator, MonthLocator, WeekdayLocator, \
     DayLocator, HourLocator, MinuteLocator, DateFormatter, DayMultiLocator, WeekMultiLocator

Line ~1475:

    def plot_date(self, d, y, converter, fmt='bo', **kwargs):
        """
        plot_date(d, y, converter, fmt='bo', **kwargs)

        d is a sequence of dates; converter is a dates.DateConverter
        instance that converts your dates to seconds since the epoch for
        plotting. y are the y values at those dates. fmt is a plot
        format string. kwargs are passed on to plot. See plot for more
        information.

        pass converter = None if your dates are already in epoch format
        """

        if not self._hold: self.cla()

        if converter is not None:
            e = array([converter.epoch(thisd) for thisd in d])
        else:
            e = d
                   assert(len(e))
        ret = self.plot(e, y, fmt, **kwargs)

        span = self.dataLim.intervalx().span()
        if span==0: span = SEC_PER_HOUR
        minutes = span/SEC_PER_MIN
        hours = span/SEC_PER_HOUR
        days = span/SEC_PER_DAY
        weeks = span/SEC_PER_WEEK
        months = span/(SEC_PER_DAY*31) # approx years = span/(SEC_PER_WEEK*52) # approx

        numticks = 5
        if years>numticks:
            locator = YearLocator(math.ceil(years/numticks)) fmt = '%Y'
        elif months>numticks:
            locator = MonthLocator(math.ceil(months/numticks)) fmt = '%b %Y'
        elif weeks>numticks:
            locator = WeekMultiLocator(math.ceil(weeks/numticks))
            fmt = '%a, %b %d'
        elif days>numticks:
            locator = DayMultiLocator(math.ceil(days/numticks))
            fmt = '%b %d'
        elif hours>numticks:
            locator = HourLocator(math.ceil(hours/numticks))
            fmt = '%H:%M\n%b %d'
        elif minutes>numticks:
            locator = MinuteLocator(math.ceil(minutes/numticks))
            fmt = '%H:%M:%S'
        else:
            locator = MinuteLocator(1)
            fmt = '%H:%M:%S'

        formatter = DateFormatter(fmt)
        self.xaxis.set_major_locator(locator)
        self.xaxis.set_major_formatter(formatter)
        self.autoscale_view()
        return ret

In ticks.py:

Need to add:

class WeekMultiLocator(MultipleLocator):
    """
    Make ticks on day which are multiples of base
    """
    def __init__(self, base):
        MultipleLocator.__init__(self, base*SEC_PER_WEEK)

···

--
Peter Groszkowski Gemini Observatory
Tel: +1 808 974-2509 670 N. A'ohoku Place
Fax: +1 808 935-9235 Hilo, Hawai'i 96720, USA