[Tex/LaTex] Plotting time series data

dateplotpgfplots

I would like to plot quarterly time series with special x-axis features. In particular, I would like to see small ticks indicating when the quarters begin and larger ticks indicating when the years begin, but would only like to label the years (and not the quarters). For illustration purposes, the following example does exactly what I want, except that the code is not flexible at all.

\documentclass{article}   
\usepackage{pgfplots}   
\usepackage{filecontents}
\pgfplotsset{width=8cm,compat=newest}   
\usepgfplotslibrary{dateplot}     

\usetikzlibrary{calc}

\begin{filecontents}{Quarterly.dat}
date    Y
2009-02-15  1
2009-05-15  2
2009-08-15  3
2009-11-15  4
2010-02-15  4
2010-05-15  4
2010-08-15  4
2010-11-15  4
2011-02-15  4
2011-05-15  4
2011-08-15  4
2011-11-15  4
2012-02-15  4
2012-05-15  4
2012-08-15  4
2012-11-15  4
2013-02-15  4
2013-05-15  4
2013-08-15  4
2013-11-15  4
\end{filecontents}

\begin{document}
\begin{tikzpicture}
\begin{axis}[
mark =none,
xmin=2008-12-01,
xmax=2012-02-01,
major tick length=4pt,
minor tick length=2pt,
date coordinates in=x,
minor x tick num=3,
xtick={2009-01-01,2010-01-01,2011-01-01,2012-01-01}, 
xticklabels= {\year,\year,\year},
x tick label style={anchor=east,xshift=1.5cm,yshift=-0.3cm},
]
\addplot [only marks,green]table [x=date,y=Y]{Quarterly.dat}; %
\end{axis}
\end{tikzpicture}
\end{document} 

The code is not flexible for several reasons. For instance, if I write xtick={2009-01-01,2010-01-01,2011-01-01,2012-01-01,2013-01-01}, the minor x tick num=3 stops working (probably because the ticks don’t have the same distance). The example also requires that I have to manually set xshift (in the example xshift=1.5cm). It would be great if the code could automatically lable all years for any quarterly time series. I also tried to use the extra x ticks option, but without success.

screenschot

Best Answer

Turns out this is a very good practice for x coord trafo/.code and x coord inv trafo/.code. Before that let me explain a little.

Your problem falls into the following three parts:

  • First, major ticks should appear at every new year eve. But dateplot knows nothing about new year.
  • Second, minor ticks appear only if major ticks are separated uniformly. But years are 365 or 366 days long.
  • Third, you want to control the layout.

There is a very easy way to get over it: make years as wide as each other. More precisely, I use 2015.09314 to represent today, Feb 3, 2015. This changes everything because:

  • New year eves are now represented by integers and pgfplots LOVES integer.
  • Years are one unit long.
  • Controlling layout is easier with something like xmin=2008.

So all you have to do is check out tikzlibrarypgfplots.dateplot.code.tex and write your own year coordinates in. In the following code, /pgfplots/#1 coord trafo is used to transform your input 2015-2-3 to a decimal number 2015.09314 so then pgfplots can plot data. On the other hand, x coord inv trafo is used to transform the decimal number to a label text. (For example MMXV instead of 2015.) (I did not do this one because the default is good enough.) (Well... I set 1000 sep to nothing in another syntax.)

\documentclass[border=1cm]{standalone}
\usepackage{pgfplots}
    \usepgfplotslibrary{dateplot}
\begin{filecontents}{\jobname-Quarterly.dat}
date Y
2009-01-01 9
2009-12-31 9
2010-01-01 0
2010-12-31 0
2014-01-01 4
2014-12-31 4
\end{filecontents}

\makeatletter
\pgfplotsset{
    /pgfplots/year coordinates in/.code={
        \pgfkeysalso{%
            #1 tick label style={/pgf/number format/1000 sep=}, % "2015" rather than "2,015"
            #1 tick label as interval,
            minor #1 tick num=11 % January, ..., December
        }
        \pgfkeysdef{/pgfplots/#1 coord trafo}{
            \begingroup
            \edef\pgfplotstempjuliandate{##1}
            % check if we also have a TIME like '2006-01-01 11:21'
            \expandafter\pgfutil@in@\expandafter:\expandafter{\pgfplotstempjuliandate}
            \ifpgfutil@in@
                % we have a TIME!
                \expandafter\pgfplotslibdateplot@map@time\pgfplotstempjuliandate:\dateto\pgfplotstempjuliandate\timeto\pgfplotstemptime
            \else
                \let\pgfplotstemptime=\pgfutil@empty
            \fi
            \expandafter\pgfcalendardatetojulian\expandafter{\pgfplotstempjuliandate}\c@pgf@counta
            \expandafter\pgfcalendardatetojulian\expandafter{\year-1-0}\c@pgf@countb
            \expandafter\pgfcalendardatetojulian\expandafter{\year-12-31}\c@pgf@countc
            \advance\c@pgf@counta by-\c@pgf@countb % now a = #days from 1/1 to temp
            \advance\c@pgf@countc by-\c@pgf@countb % now b = #days of that year
            \ifx\pgfplotstemptime\pgfutil@empty
                % no time:
                \pgfmathparse{\year+\the\c@pgf@counta/\the\c@pgf@countc}
            \else
                % add time fraction (which should be in the range
                % [0,1]).
                \ifdim\pgfplotstemptime pt<1pt
                    % discard prefix '0.':
                    \expandafter\pgfplotslibdateplot@discard@zero@dot\pgfplotstemptime\to\pgfplotstemptime
                    \pgfmathparse{\year+(\the\c@pgf@counta.\pgfplotstemptime)/\the\c@pgf@countc}%
                \else
                    % assume \pgfplotstemptime=1pt :
                    \advance\c@pgf@counta by1
                    \pgfmathparse{\year+\the\c@pgf@counta/\the\c@pgf@countc}
                \fi
            \fi
            \pgfmath@smuggleone\pgfmathresult
            \endgroup
        }
    }
}

\begin{document}
    \begin{tikzpicture}
        \begin{axis}[year coordinates in=x,minor x tick num=1]
            \addplot [only marks]table[x=date,y=Y]{\jobname-Quarterly.dat};
        \end{axis}
    \end{tikzpicture}
\end{document}
Related Question