Time Series / Date functionality — pandas 0.12.0 documentation (2024)

pandas has proven very successful as a tool for working with time series data,especially in the financial data analysis space. With the 0.8 release, we havefurther improved the time series API in pandas by leaps and bounds. Using thenew NumPy datetime64 dtype, we have consolidated a large number of featuresfrom other Python libraries like scikits.timeseries as well as createda tremendous amount of new functionality for manipulating time series data.

In working with time series data, we will frequently seek to:

  • generate sequences of fixed-frequency dates and time spans
  • conform or convert time series to a particular frequency
  • compute “relative” dates based on various non-standard time increments(e.g. 5 business days before the last business day of the year), or “roll”dates forward or backward

pandas provides a relatively compact and self-contained set of tools forperforming the above tasks.

Create a range of dates:

# 72 hours starting with midnight Jan 1st, 2011In [1]: rng = date_range('1/1/2011', periods=72, freq='H')In [2]: rng[:5]<class 'pandas.tseries.index.DatetimeIndex'>[2011-01-01 00:00:00, ..., 2011-01-01 04:00:00]Length: 5, Freq: H, Timezone: None

Index pandas objects with dates:

In [3]: ts = Series(randn(len(rng)), index=rng)In [4]: ts.head()2011-01-01 00:00:00 0.4691122011-01-01 01:00:00 -0.2828632011-01-01 02:00:00 -1.5090592011-01-01 03:00:00 -1.1356322011-01-01 04:00:00 1.212112Freq: H, dtype: float64

Change frequency and fill gaps:

# to 45 minute frequency and forward fillIn [5]: converted = ts.asfreq('45Min', method='pad')In [6]: converted.head()2011-01-01 00:00:00 0.4691122011-01-01 00:45:00 0.4691122011-01-01 01:30:00 -0.2828632011-01-01 02:15:00 -1.5090592011-01-01 03:00:00 -1.135632Freq: 45T, dtype: float64

Resample:

# Daily meansIn [7]: ts.resample('D', how='mean')2011-01-01 -0.3195692011-01-02 -0.3377032011-01-03 0.117258Freq: D, dtype: float64

Time Stamps vs. Time Spans

Time-stamped data is the most basic type of timeseries data that associatesvalues with points in time. For pandas objects it means using the points intime to create the index

In [8]: dates = [datetime(2012, 5, 1), datetime(2012, 5, 2), datetime(2012, 5, 3)]In [9]: ts = Series(np.random.randn(3), dates)In [10]: type(ts.index)pandas.tseries.index.DatetimeIndexIn [11]: ts2012-05-01 -0.4100012012-05-02 -0.0786382012-05-03 0.545952dtype: float64

However, in many cases it is more natural to associate things like changevariables with a time span instead.

For example:

In [12]: periods = PeriodIndex([Period('2012-01'), Period('2012-02'), ....: Period('2012-03')]) ....: In [13]: ts = Series(np.random.randn(3), periods)In [14]: type(ts.index)pandas.tseries.period.PeriodIndexIn [15]: ts2012-01 -1.2192172012-02 -1.2268252012-03 0.769804Freq: M, dtype: float64

Starting with 0.8, pandas allows you to capture both representations andconvert between them. Under the hood, pandas represents timestamps usinginstances of Timestamp and sequences of timestamps using instances ofDatetimeIndex. For regular time spans, pandas uses Period objects forscalar values and PeriodIndex for sequences of spans. Better support forirregular intervals with arbitrary start and end points are forth-coming infuture releases.

Converting to Timestamps

To convert a Series or list-like object of date-like objects e.g. strings,epochs, or a mixture, you can use the to_datetime function. When passeda Series, this returns a Series (with the same index), while a list-likeis converted to a DatetimeIndex:

In [16]: to_datetime(Series(['Jul 31, 2009', '2010-01-10', None]))0 2009-07-31 00:00:001 2010-01-10 00:00:002 NaTdtype: datetime64[ns]In [17]: to_datetime(['2005/11/23', '2010.12.31'])<class 'pandas.tseries.index.DatetimeIndex'>[2005-11-23 00:00:00, 2010-12-31 00:00:00]Length: 2, Freq: None, Timezone: None

If you use dates which start with the day first (i.e. European style),you can pass the dayfirst flag:

In [18]: to_datetime(['04-01-2012 10:00'], dayfirst=True)<class 'pandas.tseries.index.DatetimeIndex'>[2012-01-04 10:00:00]Length: 1, Freq: None, Timezone: NoneIn [19]: to_datetime(['14-01-2012', '01-14-2012'], dayfirst=True)<class 'pandas.tseries.index.DatetimeIndex'>[2012-01-14 00:00:00, 2012-01-14 00:00:00]Length: 2, Freq: None, Timezone: None

Warning

You see in the above example that dayfirst isn’t strict, so if a datecan’t be parsed with the day being first it will be parsed as ifdayfirst were False.

Pass coerce=True to convert bad data to NaT (not a time):

In [20]: to_datetime(['2009-07-31', 'asd'])array(['2009-07-31', 'asd'], dtype=object)In [21]: to_datetime(['2009-07-31', 'asd'], coerce=True)<class 'pandas.tseries.index.DatetimeIndex'>[2009-07-31 00:00:00, NaT]Length: 2, Freq: None, Timezone: None

It’s also possible to convert integer or float epoch times. The default unitfor these is nanoseconds (since these are how Timestamps are stored). However,often epochs are stored in another unit which can be specified:

In [22]: to_datetime([1])<class 'pandas.tseries.index.DatetimeIndex'>[1970-01-01 00:00:00.000000001]Length: 1, Freq: None, Timezone: NoneIn [23]: to_datetime([1, 3.14], unit='s')<class 'pandas.tseries.index.DatetimeIndex'>[1970-01-01 00:00:01, 1970-01-01 00:00:03.140000]Length: 2, Freq: None, Timezone: None

Note

Epoch times will be rounded to the nearest nanosecond.

Take care, to_datetime may not act as you expect on mixed data:

In [24]: pd.to_datetime([1, '1'])<class 'pandas.tseries.index.DatetimeIndex'>[1970-01-01 00:00:00.000000001, 2014-01-01 00:00:00]Length: 2, Freq: None, Timezone: None

Generating Ranges of Timestamps

To generate an index with time stamps, you can use either the DatetimeIndex orIndex constructor and pass in a list of datetime objects:

In [25]: dates = [datetime(2012, 5, 1), datetime(2012, 5, 2), datetime(2012, 5, 3)]In [26]: index = DatetimeIndex(dates)In [27]: index # Note the frequency information<class 'pandas.tseries.index.DatetimeIndex'>[2012-05-01 00:00:00, ..., 2012-05-03 00:00:00]Length: 3, Freq: None, Timezone: NoneIn [28]: index = Index(dates)In [29]: index # Automatically converted to DatetimeIndex<class 'pandas.tseries.index.DatetimeIndex'>[2012-05-01 00:00:00, ..., 2012-05-03 00:00:00]Length: 3, Freq: None, Timezone: None

Practically, this becomes very cumbersome because we often need a very longindex with a large number of timestamps. If we need timestamps on a regularfrequency, we can use the pandas functions date_range and bdate_rangeto create timestamp indexes.

In [30]: index = date_range('2000-1-1', periods=1000, freq='M')In [31]: index<class 'pandas.tseries.index.DatetimeIndex'>[2000-01-31 00:00:00, ..., 2083-04-30 00:00:00]Length: 1000, Freq: M, Timezone: NoneIn [32]: index = bdate_range('2012-1-1', periods=250)In [33]: index<class 'pandas.tseries.index.DatetimeIndex'>[2012-01-02 00:00:00, ..., 2012-12-14 00:00:00]Length: 250, Freq: B, Timezone: None

Convenience functions like date_range and bdate_range utilize avariety of frequency aliases. The default frequency for date_range is acalendar day while the default for bdate_range is a business day

In [34]: start = datetime(2011, 1, 1)In [35]: end = datetime(2012, 1, 1)In [36]: rng = date_range(start, end)In [37]: rng<class 'pandas.tseries.index.DatetimeIndex'>[2011-01-01 00:00:00, ..., 2012-01-01 00:00:00]Length: 366, Freq: D, Timezone: NoneIn [38]: rng = bdate_range(start, end)In [39]: rng<class 'pandas.tseries.index.DatetimeIndex'>[2011-01-03 00:00:00, ..., 2011-12-30 00:00:00]Length: 260, Freq: B, Timezone: None

date_range and bdate_range makes it easy to generate a range of datesusing various combinations of parameters like start, end,periods, and freq:

In [40]: date_range(start, end, freq='BM')<class 'pandas.tseries.index.DatetimeIndex'>[2011-01-31 00:00:00, ..., 2011-12-30 00:00:00]Length: 12, Freq: BM, Timezone: NoneIn [41]: date_range(start, end, freq='W')<class 'pandas.tseries.index.DatetimeIndex'>[2011-01-02 00:00:00, ..., 2012-01-01 00:00:00]Length: 53, Freq: W-SUN, Timezone: NoneIn [42]: bdate_range(end=end, periods=20)<class 'pandas.tseries.index.DatetimeIndex'>[2011-12-05 00:00:00, ..., 2011-12-30 00:00:00]Length: 20, Freq: B, Timezone: NoneIn [43]: bdate_range(start=start, periods=20)<class 'pandas.tseries.index.DatetimeIndex'>[2011-01-03 00:00:00, ..., 2011-01-28 00:00:00]Length: 20, Freq: B, Timezone: None

The start and end dates are strictly inclusive. So it will not generate anydates outside of those dates if specified.

DatetimeIndex

One of the main uses for DatetimeIndex is as an index for pandas objects.The DatetimeIndex class contains many timeseries related optimizations:

  • A large range of dates for various offsets are pre-computed and cachedunder the hood in order to make generating subsequent date ranges very fast(just have to grab a slice)
  • Fast shifting using the shift and tshift method on pandas objects
  • Unioning of overlapping DatetimeIndex objects with the same frequency isvery fast (important for fast data alignment)
  • Quick access to date fields via properties such as year, month, etc.
  • Regularization functions like snap and very fast asof logic

DatetimeIndex objects has all the basic functionality of regular Index objectsand a smorgasbord of advanced timeseries-specific methods for easy frequencyprocessing.

See also

Reindexing methods

Note

While pandas does not force you to have a sorted date index, some of thesemethods may have unexpected or incorrect behavior if the dates areunsorted. So please be careful.

DatetimeIndex can be used like a regular index and offers all of itsintelligent functionality like selection, slicing, etc.

In [44]: rng = date_range(start, end, freq='BM')In [45]: ts = Series(randn(len(rng)), index=rng)In [46]: ts.index<class 'pandas.tseries.index.DatetimeIndex'>[2011-01-31 00:00:00, ..., 2011-12-30 00:00:00]Length: 12, Freq: BM, Timezone: NoneIn [47]: ts[:5].index<class 'pandas.tseries.index.DatetimeIndex'>[2011-01-31 00:00:00, ..., 2011-05-31 00:00:00]Length: 5, Freq: BM, Timezone: NoneIn [48]: ts[::2].index<class 'pandas.tseries.index.DatetimeIndex'>[2011-01-31 00:00:00, ..., 2011-11-30 00:00:00]Length: 6, Freq: 2BM, Timezone: None

Partial String Indexing

You can pass in dates and strings that parse to dates as indexing parameters:

In [49]: ts['1/31/2011']-1.2812473076599531In [50]: ts[datetime(2011, 12, 25):]2011-12-30 0.687738Freq: BM, dtype: float64In [51]: ts['10/31/2011':'12/31/2011']2011-10-31 0.1497482011-11-30 -0.7323392011-12-30 0.687738Freq: BM, dtype: float64

To provide convenience for accessing longer time series, you can also pass inthe year or year and month as strings:

In [52]: ts['2011']2011-01-31 -1.2812472011-02-28 -0.7277072011-03-31 -0.1213062011-04-29 -0.0978832011-05-31 0.6957752011-06-30 0.3417342011-07-29 0.9597262011-08-31 -1.1103362011-09-30 -0.6199762011-10-31 0.1497482011-11-30 -0.7323392011-12-30 0.687738Freq: BM, dtype: float64In [53]: ts['2011-6']2011-06-30 0.341734Freq: BM, dtype: float64

This type of slicing will work on a DataFrame with a DateTimeIndex as well. Since thepartial string selection is a form of label slicing, the endpoints will be included. Thiswould include matching times on an included date. Here’s an example:

In [54]: dft = DataFrame(randn(100000,1),columns=['A'],index=date_range('20130101',periods=100000,freq='T'))In [55]: dft<class 'pandas.core.frame.DataFrame'>DatetimeIndex: 100000 entries, 2013-01-01 00:00:00 to 2013-03-11 10:39:00Freq: TData columns (total 1 columns):A 100000 non-null valuesdtypes: float64(1)In [56]: dft['2013']<class 'pandas.core.frame.DataFrame'>DatetimeIndex: 100000 entries, 2013-01-01 00:00:00 to 2013-03-11 10:39:00Freq: TData columns (total 1 columns):A 100000 non-null valuesdtypes: float64(1)

This starts on the very first time in the month, and includes the last date & time for the month

In [57]: dft['2013-1':'2013-2']<class 'pandas.core.frame.DataFrame'>DatetimeIndex: 84960 entries, 2013-01-01 00:00:00 to 2013-02-28 23:59:00Freq: TData columns (total 1 columns):A 84960 non-null valuesdtypes: float64(1)

This specifies a stop time that includes all of the times on the last day

In [58]: dft['2013-1':'2013-2-28']<class 'pandas.core.frame.DataFrame'>DatetimeIndex: 84960 entries, 2013-01-01 00:00:00 to 2013-02-28 23:59:00Freq: TData columns (total 1 columns):A 84960 non-null valuesdtypes: float64(1)

This specifies an exact stop time (and is not the same as the above)

In [59]: dft['2013-1':'2013-2-28 00:00:00']<class 'pandas.core.frame.DataFrame'>DatetimeIndex: 83521 entries, 2013-01-01 00:00:00 to 2013-02-28 00:00:00Freq: TData columns (total 1 columns):A 83521 non-null valuesdtypes: float64(1)

We are stopping on the included end-point as its part of the index

In [60]: dft['2013-1-15':'2013-1-15 12:30:00']<class 'pandas.core.frame.DataFrame'>DatetimeIndex: 751 entries, 2013-01-15 00:00:00 to 2013-01-15 12:30:00Freq: TData columns (total 1 columns):A 751 non-null valuesdtypes: float64(1)

Warning

The following selection will raises a KeyError; otherwise this selection methodologywould be inconsistent with other selection methods in pandas (as this is not a slice, nor does itresolve to one)

dft['2013-1-15 12:30:00']

To select a single row, use .loc

In [61]: dft.loc['2013-1-15 12:30:00']A 0.193284Name: 2013-01-15 12:30:00, dtype: float64

Datetime Indexing

Indexing a DateTimeIndex with a partial string depends on the “accuracy” of the period, in other words how specific the interval is in relation to the frequency of the index. In contrast, indexing with datetime objects is exact, because the objects have exact meaning. These also follow the sematics of including both endpoints.

These datetime objects are specific hours, minutes, and seconds even though they were not explicity specified (they are 0).

In [62]: dft[datetime(2013, 1, 1):datetime(2013,2,28)]<class 'pandas.core.frame.DataFrame'>DatetimeIndex: 83521 entries, 2013-01-01 00:00:00 to 2013-02-28 00:00:00Freq: TData columns (total 1 columns):A 83521 non-null valuesdtypes: float64(1)

With no defaults.

In [63]: dft[datetime(2013, 1, 1, 10, 12, 0):datetime(2013, 2, 28, 10, 12, 0)]<class 'pandas.core.frame.DataFrame'>DatetimeIndex: 83521 entries, 2013-01-01 10:12:00 to 2013-02-28 10:12:00Freq: TData columns (total 1 columns):A 83521 non-null valuesdtypes: float64(1)

Truncating & Fancy Indexing

A truncate convenience function is provided that is equivalent to slicing:

In [64]: ts.truncate(before='10/31/2011', after='12/31/2011')2011-10-31 0.1497482011-11-30 -0.7323392011-12-30 0.687738Freq: BM, dtype: float64

Even complicated fancy indexing that breaks the DatetimeIndex’s frequencyregularity will result in a DatetimeIndex (but frequency is lost):

In [65]: ts[[0, 2, 6]].index<class 'pandas.tseries.index.DatetimeIndex'>[2011-01-31 00:00:00, ..., 2011-07-29 00:00:00]Length: 3, Freq: None, Timezone: None

DateOffset objects

In the preceding examples, we created DatetimeIndex objects at variousfrequencies by passing in frequency strings like ‘M’, ‘W’, and ‘BM to thefreq keyword. Under the hood, these frequency strings are being translatedinto an instance of pandas DateOffset, which represents a regularfrequency increment. Specific offset logic like “month”, “business day”, or“one hour” is represented in its various subclasses.

Class nameDescription
DateOffsetGeneric offset class, defaults to 1 calendar day
BDaybusiness day (weekday)
CDaycustom business day (experimental)
Weekone week, optionally anchored on a day of the week
WeekOfMonththe x-th day of the y-th week of each month
MonthEndcalendar month end
MonthBegincalendar month begin
BMonthEndbusiness month end
BMonthBeginbusiness month begin
QuarterEndcalendar quarter end
QuarterBegincalendar quarter begin
BQuarterEndbusiness quarter end
BQuarterBeginbusiness quarter begin
YearEndcalendar year end
YearBegincalendar year begin
BYearEndbusiness year end
BYearBeginbusiness year begin
Hourone hour
Minuteone minute
Secondone second
Millione millisecond
Microone microsecond

The basic DateOffset takes the same arguments asdateutil.relativedelta, which works like:

In [66]: d = datetime(2008, 8, 18)In [67]: d + relativedelta(months=4, days=5)datetime.datetime(2008, 12, 23, 0, 0)

We could have done the same thing with DateOffset:

In [68]: from pandas.tseries.offsets import *In [69]: d + DateOffset(months=4, days=5)datetime.datetime(2008, 12, 23, 0, 0)

The key features of a DateOffset object are:

  • it can be added / subtracted to/from a datetime object to obtain ashifted date
  • it can be multiplied by an integer (positive or negative) so that theincrement will be applied multiple times
  • it has rollforward and rollback methods for moving a date forwardor backward to the next or previous “offset date”

Subclasses of DateOffset define the apply function which dictatescustom date increment logic, such as adding business days:

class BDay(DateOffset): """DateOffset increments between business days""" def apply(self, other): ...
In [70]: d - 5 * BDay()datetime.datetime(2008, 8, 11, 0, 0)In [71]: d + BMonthEnd()datetime.datetime(2008, 8, 29, 0, 0)

The rollforward and rollback methods do exactly what you would expect:

In [72]: ddatetime.datetime(2008, 8, 18, 0, 0)In [73]: offset = BMonthEnd()In [74]: offset.rollforward(d)datetime.datetime(2008, 8, 29, 0, 0)In [75]: offset.rollback(d)datetime.datetime(2008, 7, 31, 0, 0)

It’s definitely worth exploring the pandas.tseries.offsets module and thevarious docstrings for the classes.

Parametric offsets

Some of the offsets can be “parameterized” when created to result in differentbehavior. For example, the Week offset for generating weekly data accepts aweekday parameter which results in the generated dates always lying on aparticular day of the week:

In [76]: d + Week()datetime.datetime(2008, 8, 25, 0, 0)In [77]: d + Week(weekday=4)datetime.datetime(2008, 8, 22, 0, 0)In [78]: (d + Week(weekday=4)).weekday()4

Another example is parameterizing YearEnd with the specific ending month:

In [79]: d + YearEnd()datetime.datetime(2008, 12, 31, 0, 0)In [80]: d + YearEnd(month=6)datetime.datetime(2009, 6, 30, 0, 0)

Custom Business Days (Experimental)

The CDay or CustomBusinessDay class provides a parametricBusinessDay class which can be used to create customized business daycalendars which account for local holidays and local weekend conventions.

In [81]: from pandas.tseries.offsets import CustomBusinessDay# As an interesting example, let's look at Egypt where# a Friday-Saturday weekend is observed.In [82]: weekmask_egypt = 'Sun Mon Tue Wed Thu'# They also observe International Workers' Day so let's# add that for a couple of yearsIn [83]: holidays = ['2012-05-01', datetime(2013, 5, 1), np.datetime64('2014-05-01')]In [84]: bday_egypt = CustomBusinessDay(holidays=holidays, weekmask=weekmask_egypt)In [85]: dt = datetime(2013, 4, 30)In [86]: print dt + 2 * bday_egypt2013-05-05 00:00:00In [87]: dts = date_range(dt, periods=5, freq=bday_egypt).to_series()In [88]: print dts2013-04-30 2013-04-30 00:00:002013-05-02 2013-05-02 00:00:002013-05-05 2013-05-05 00:00:002013-05-06 2013-05-06 00:00:002013-05-07 2013-05-07 00:00:00Freq: C, dtype: datetime64[ns]In [89]: print Series(dts.weekday, dts).map(Series('Mon Tue Wed Thu Fri Sat Sun'.split()))2013-04-30 Tue2013-05-02 Thu2013-05-05 Sun2013-05-06 Mon2013-05-07 Tuedtype: object

Note

The frequency string ‘C’ is used to indicate that a CustomBusinessDayDateOffset is used, it is important to note that since CustomBusinessDay isa parameterised type, instances of CustomBusinessDay may differ and this isnot detectable from the ‘C’ frequency string. The user therefore needs toensure that the ‘C’ frequency string is used consistently within the user’sapplication.

Note

This uses the numpy.busdaycalendar API introduced in Numpy 1.7 andtherefore requires Numpy 1.7.0 or newer.

Warning

There are known problems with the timezone handling in Numpy 1.7 and usersshould therefore use this experimental(!) feature with caution and attheir own risk.

To the extent that the datetime64 and busdaycalendar APIs in Numpyhave to change to fix the timezone issues, the behaviour of theCustomBusinessDay class may have to change in future versions.

Offset Aliases

A number of string aliases are given to useful common time seriesfrequencies. We will refer to these aliases as offset aliases(referred to as time rules prior to v0.8.0).

AliasDescription
Bbusiness day frequency
Ccustom business day frequency (experimental)
Dcalendar day frequency
Wweekly frequency
Mmonth end frequency
BMbusiness month end frequency
MSmonth start frequency
BMSbusiness month start frequency
Qquarter end frequency
BQbusiness quarter endfrequency
QSquarter start frequency
BQSbusiness quarter start frequency
Ayear end frequency
BAbusiness year end frequency
ASyear start frequency
BASbusiness year start frequency
Hhourly frequency
Tminutely frequency
Ssecondly frequency
Lmilliseonds
Umicroseconds

Combining Aliases

As we have seen previously, the alias and the offset instance are fungible inmost functions:

In [90]: date_range(start, periods=5, freq='B')<class 'pandas.tseries.index.DatetimeIndex'>[2011-01-03 00:00:00, ..., 2011-01-07 00:00:00]Length: 5, Freq: B, Timezone: NoneIn [91]: date_range(start, periods=5, freq=BDay())<class 'pandas.tseries.index.DatetimeIndex'>[2011-01-03 00:00:00, ..., 2011-01-07 00:00:00]Length: 5, Freq: B, Timezone: None

You can combine together day and intraday offsets:

In [92]: date_range(start, periods=10, freq='2h20min')<class 'pandas.tseries.index.DatetimeIndex'>[2011-01-01 00:00:00, ..., 2011-01-01 21:00:00]Length: 10, Freq: 140T, Timezone: NoneIn [93]: date_range(start, periods=10, freq='1D10U')<class 'pandas.tseries.index.DatetimeIndex'>[2011-01-01 00:00:00, ..., 2011-01-10 00:00:00.000090]Length: 10, Freq: 86400000010U, Timezone: None

Anchored Offsets

For some frequencies you can specify an anchoring suffix:

AliasDescription
W-SUNweekly frequency (sundays). Same as ‘W’
W-MONweekly frequency (mondays)
W-TUEweekly frequency (tuesdays)
W-WEDweekly frequency (wednesdays)
W-THUweekly frequency (thursdays)
W-FRIweekly frequency (fridays)
W-SATweekly frequency (saturdays)
(B)Q(S)-DECquarterly frequency, year ends in December. Same as ‘Q’
(B)Q(S)-JANquarterly frequency, year ends in January
(B)Q(S)-FEBquarterly frequency, year ends in February
(B)Q(S)-MARquarterly frequency, year ends in March
(B)Q(S)-APRquarterly frequency, year ends in April
(B)Q(S)-MAYquarterly frequency, year ends in May
(B)Q(S)-JUNquarterly frequency, year ends in June
(B)Q(S)-JULquarterly frequency, year ends in July
(B)Q(S)-AUGquarterly frequency, year ends in August
(B)Q(S)-SEPquarterly frequency, year ends in September
(B)Q(S)-OCTquarterly frequency, year ends in October
(B)Q(S)-NOVquarterly frequency, year ends in November
(B)A(S)-DECannual frequency, anchored end of December. Same as ‘A’
(B)A(S)-JANannual frequency, anchored end of January
(B)A(S)-FEBannual frequency, anchored end of February
(B)A(S)-MARannual frequency, anchored end of March
(B)A(S)-APRannual frequency, anchored end of April
(B)A(S)-MAYannual frequency, anchored end of May
(B)A(S)-JUNannual frequency, anchored end of June
(B)A(S)-JULannual frequency, anchored end of July
(B)A(S)-AUGannual frequency, anchored end of August
(B)A(S)-SEPannual frequency, anchored end of September
(B)A(S)-OCTannual frequency, anchored end of October
(B)A(S)-NOVannual frequency, anchored end of November

These can be used as arguments to date_range, bdate_range, constructorsfor DatetimeIndex, as well as various other timeseries-related functionsin pandas.

Legacy Aliases

Note that prior to v0.8.0, time rules had a slightly different look. Pandaswill continue to support the legacy time rules for the time being but it isstrongly recommended that you switch to using the new offset aliases.

Legacy Time RuleOffset Alias
WEEKDAYB
EOMBM
W@MONW-MON
W@TUEW-TUE
W@WEDW-WED
W@THUW-THU
W@FRIW-FRI
W@SATW-SAT
W@SUNW-SUN
Q@JANBQ-JAN
Q@FEBBQ-FEB
Q@MARBQ-MAR
A@JANBA-JAN
A@FEBBA-FEB
A@MARBA-MAR
A@APRBA-APR
A@MAYBA-MAY
A@JUNBA-JUN
A@JULBA-JUL
A@AUGBA-AUG
A@SEPBA-SEP
A@OCTBA-OCT
A@NOVBA-NOV
A@DECBA-DEC
minT
msL
usU

As you can see, legacy quarterly and annual frequencies are business quarterand business year ends. Please also note the legacy time rule for millisecondsms versus the new offset alias for month start MS. This means thatoffset alias parsing is case sensitive.

Time series-related instance methods

Shifting / lagging

One may want to shift or lag the values in a TimeSeries back and forward intime. The method for this is shift, which is available on all of the pandasobjects. In DataFrame, shift will currently only shift along the indexand in Panel along the major_axis.

In [94]: ts = ts[:5]In [95]: ts.shift(1)2011-01-31 NaN2011-02-28 -1.2812472011-03-31 -0.7277072011-04-29 -0.1213062011-05-31 -0.097883Freq: BM, dtype: float64

The shift method accepts an freq argument which can accept aDateOffset class or other timedelta-like object or also a offset alias:

In [96]: ts.shift(5, freq=datetools.bday)2011-02-07 -1.2812472011-03-07 -0.7277072011-04-07 -0.1213062011-05-06 -0.0978832011-06-07 0.695775dtype: float64In [97]: ts.shift(5, freq='BM')2011-06-30 -1.2812472011-07-29 -0.7277072011-08-31 -0.1213062011-09-30 -0.0978832011-10-31 0.695775Freq: BM, dtype: float64

Rather than changing the alignment of the data and the index, DataFrame andTimeSeries objects also have a tshift convenience method that changesall the dates in the index by a specified number of offsets:

In [98]: ts.tshift(5, freq='D')2011-02-05 -1.2812472011-03-05 -0.7277072011-04-05 -0.1213062011-05-04 -0.0978832011-06-05 0.695775dtype: float64

Note that with tshift, the leading entry is no longer NaN because the datais not being realigned.

Frequency conversion

The primary function for changing frequencies is the asfreq function.For a DatetimeIndex, this is basically just a thin, but convenient wrapperaround reindex which generates a date_range and calls reindex.

In [99]: dr = date_range('1/1/2010', periods=3, freq=3 * datetools.bday)In [100]: ts = Series(randn(3), index=dr)In [101]: ts2010-01-01 -0.6595742010-01-06 1.4945222010-01-11 -0.778425Freq: 3B, dtype: float64In [102]: ts.asfreq(BDay())2010-01-01 -0.6595742010-01-04 NaN2010-01-05 NaN2010-01-06 1.4945222010-01-07 NaN2010-01-08 NaN2010-01-11 -0.778425Freq: B, dtype: float64

asfreq provides a further convenience so you can specify an interpolationmethod for any gaps that may appear after the frequency conversion

In [103]: ts.asfreq(BDay(), method='pad')2010-01-01 -0.6595742010-01-04 -0.6595742010-01-05 -0.6595742010-01-06 1.4945222010-01-07 1.4945222010-01-08 1.4945222010-01-11 -0.778425Freq: B, dtype: float64

Filling forward / backward

Related to asfreq and reindex is the fillna function documented inthe missing data section.

Converting to Python datetimes

DatetimeIndex can be converted to an array of Python native datetime.datetime objects using theto_pydatetime method.

Up- and downsampling

With 0.8, pandas introduces simple, powerful, and efficient functionality forperforming resampling operations during frequency conversion (e.g., convertingsecondly data into 5-minutely data). This is extremely common in, but notlimited to, financial applications.

See some cookbook examples for some advanced strategies

In [104]: rng = date_range('1/1/2012', periods=100, freq='S')In [105]: ts = Series(randint(0, 500, len(rng)), index=rng)In [106]: ts.resample('5Min', how='sum')2012-01-01 25103Freq: 5T, dtype: int64

The resample function is very flexible and allows you to specify manydifferent parameters to control the frequency conversion and resamplingoperation.

The how parameter can be a function name or numpy array function that takesan array and produces aggregated values:

In [107]: ts.resample('5Min') # default is mean2012-01-01 251.03Freq: 5T, dtype: float64In [108]: ts.resample('5Min', how='ohlc') open high low close2012-01-01 308 460 9 205In [109]: ts.resample('5Min', how=np.max)2012-01-01 NaNFreq: 5T, dtype: float64

Any function available via dispatching can be given tothe how parameter by name, including sum, mean, std, max,min, median, first, last, ohlc.

For downsampling, closed can be set to ‘left’ or ‘right’ to specify whichend of the interval is closed:

In [110]: ts.resample('5Min', closed='right')2011-12-31 23:55:00 308.0000002012-01-01 00:00:00 250.454545Freq: 5T, dtype: float64In [111]: ts.resample('5Min', closed='left')2012-01-01 251.03Freq: 5T, dtype: float64

For upsampling, the fill_method and limit parameters can be specifiedto interpolate over the gaps that are created:

# from secondly to every 250 millisecondsIn [112]: ts[:2].resample('250L')2012-01-01 00:00:00 3082012-01-01 00:00:00.250000 NaN2012-01-01 00:00:00.500000 NaN2012-01-01 00:00:00.750000 NaN2012-01-01 00:00:01 204Freq: 250L, dtype: float64In [113]: ts[:2].resample('250L', fill_method='pad')2012-01-01 00:00:00 3082012-01-01 00:00:00.250000 3082012-01-01 00:00:00.500000 3082012-01-01 00:00:00.750000 3082012-01-01 00:00:01 204Freq: 250L, dtype: int64In [114]: ts[:2].resample('250L', fill_method='pad', limit=2)2012-01-01 00:00:00 3082012-01-01 00:00:00.250000 3082012-01-01 00:00:00.500000 3082012-01-01 00:00:00.750000 NaN2012-01-01 00:00:01 204Freq: 250L, dtype: float64

Parameters like label and loffset are used to manipulate the resultinglabels. label specifies whether the result is labeled with the beginning orthe end of the interval. loffset performs a time adjustment on the outputlabels.

In [115]: ts.resample('5Min') # by default label='right'2012-01-01 251.03Freq: 5T, dtype: float64In [116]: ts.resample('5Min', label='left')2012-01-01 251.03Freq: 5T, dtype: float64In [117]: ts.resample('5Min', label='left', loffset='1s')2012-01-01 00:00:01 251.03dtype: float64

The axis parameter can be set to 0 or 1 and allows you to resample thespecified axis for a DataFrame.

kind can be set to ‘timestamp’ or ‘period’ to convert the resulting indexto/from time-stamp and time-span representations. By default resampleretains the input representation.

convention can be set to ‘start’ or ‘end’ when resampling period data(detail below). It specifies how low frequency periods are converted to higherfrequency periods.

Note that 0.8 marks a watershed in the timeseries functionality in pandas. Inprevious versions, resampling had to be done using a combination ofdate_range, groupby with asof, and then calling an aggregationfunction on the grouped object. This was not nearly convenient or performant asthe new pandas timeseries API.

Time Span Representation

Regular intervals of time are represented by Period objects in pandas whilesequences of Period objects are collected in a PeriodIndex, which canbe created with the convenience function period_range.

Period

A Period represents a span of time (e.g., a day, a month, a quarter, etc).It can be created using a frequency alias:

In [118]: Period('2012', freq='A-DEC')Period('2012', 'A-DEC')In [119]: Period('2012-1-1', freq='D')Period('2012-01-01', 'D')In [120]: Period('2012-1-1 19:00', freq='H')Period('2012-01-01 19:00', 'H')

Unlike time stamped data, pandas does not support frequencies at multiples ofDateOffsets (e.g., ‘3Min’) for periods.

Adding and subtracting integers from periods shifts the period by its ownfrequency.

In [121]: p = Period('2012', freq='A-DEC')In [122]: p + 1Period('2013', 'A-DEC')In [123]: p - 3Period('2009', 'A-DEC')

Taking the difference of Period instances with the same frequency willreturn the number of frequency units between them:

In [124]: Period('2012', freq='A-DEC') - Period('2002', freq='A-DEC')10

PeriodIndex and period_range

Regular sequences of Period objects can be collected in a PeriodIndex,which can be constructed using the period_range convenience function:

In [125]: prng = period_range('1/1/2011', '1/1/2012', freq='M')In [126]: prng<class 'pandas.tseries.period.PeriodIndex'>freq: M[2011-01, ..., 2012-01]length: 13

The PeriodIndex constructor can also be used directly:

In [127]: PeriodIndex(['2011-1', '2011-2', '2011-3'], freq='M')<class 'pandas.tseries.period.PeriodIndex'>freq: M[2011-01, ..., 2011-03]length: 3

Just like DatetimeIndex, a PeriodIndex can also be used to index pandasobjects:

In [128]: Series(randn(len(prng)), prng)2011-01 -0.2533552011-02 -1.4269082011-03 1.5489712011-04 -0.0887182011-05 -1.7713482011-06 -0.9893282011-07 -1.5847892011-08 -0.2887862011-09 -2.0298062011-10 -0.7612002011-11 -1.6036082011-12 1.7561712012-01 0.256502Freq: M, dtype: float64

Frequency Conversion and Resampling with PeriodIndex

The frequency of Periods and PeriodIndex can be converted via the asfreqmethod. Let’s start with the fiscal year 2011, ending in December:

In [129]: p = Period('2011', freq='A-DEC')In [130]: pPeriod('2011', 'A-DEC')

We can convert it to a monthly frequency. Using the how parameter, we canspecify whether to return the starting or ending month:

In [131]: p.asfreq('M', how='start')Period('2011-01', 'M')In [132]: p.asfreq('M', how='end')Period('2011-12', 'M')

The shorthands ‘s’ and ‘e’ are provided for convenience:

In [133]: p.asfreq('M', 's')Period('2011-01', 'M')In [134]: p.asfreq('M', 'e')Period('2011-12', 'M')

Converting to a “super-period” (e.g., annual frequency is a super-period ofquarterly frequency) automatically returns the super-period that includes theinput period:

In [135]: p = Period('2011-12', freq='M')In [136]: p.asfreq('A-NOV')Period('2012', 'A-NOV')

Note that since we converted to an annual frequency that ends the year inNovember, the monthly period of December 2011 is actually in the 2012 A-NOVperiod.

Period conversions with anchored frequencies are particularly useful forworking with various quarterly data common to economics, business, and otherfields. Many organizations define quarters relative to the month in which theirfiscal year start and ends. Thus, first quarter of 2011 could start in 2010 ora few months into 2011. Via anchored frequencies, pandas works all quarterlyfrequencies Q-JAN through Q-DEC.

Q-DEC define regular calendar quarters:

In [137]: p = Period('2012Q1', freq='Q-DEC')In [138]: p.asfreq('D', 's')Period('2012-01-01', 'D')In [139]: p.asfreq('D', 'e')Period('2012-03-31', 'D')

Q-MAR defines fiscal year end in March:

In [140]: p = Period('2011Q4', freq='Q-MAR')In [141]: p.asfreq('D', 's')Period('2011-01-01', 'D')In [142]: p.asfreq('D', 'e')Period('2011-03-31', 'D')

Converting between Representations

Timestamped data can be converted to PeriodIndex-ed data using to_periodand vice-versa using to_timestamp:

In [143]: rng = date_range('1/1/2012', periods=5, freq='M')In [144]: ts = Series(randn(len(rng)), index=rng)In [145]: ts2012-01-31 0.0206012012-02-29 -0.4117192012-03-31 2.0794132012-04-30 -1.0779112012-05-31 0.099258Freq: M, dtype: float64In [146]: ps = ts.to_period()In [147]: ps2012-01 0.0206012012-02 -0.4117192012-03 2.0794132012-04 -1.0779112012-05 0.099258Freq: M, dtype: float64In [148]: ps.to_timestamp()2012-01-01 0.0206012012-02-01 -0.4117192012-03-01 2.0794132012-04-01 -1.0779112012-05-01 0.099258Freq: MS, dtype: float64

Remember that ‘s’ and ‘e’ can be used to return the timestamps at the start orend of the period:

In [149]: ps.to_timestamp('D', how='s')2012-01-01 0.0206012012-02-01 -0.4117192012-03-01 2.0794132012-04-01 -1.0779112012-05-01 0.099258Freq: MS, dtype: float64

Converting between period and timestamp enables some convenient arithmeticfunctions to be used. In the following example, we convert a quarterlyfrequency with year ending in November to 9am of the end of the month followingthe quarter end:

In [150]: prng = period_range('1990Q1', '2000Q4', freq='Q-NOV')In [151]: ts = Series(randn(len(prng)), prng)In [152]: ts.index = (prng.asfreq('M', 'e') + 1).asfreq('H', 's') + 9In [153]: ts.head()1990-03-01 09:00 -0.0898511990-06-01 09:00 0.7113291990-09-01 09:00 0.5317611990-12-01 09:00 0.2656151991-03-01 09:00 -0.174462Freq: H, dtype: float64

Time Zone Handling

Using pytz, pandas provides rich support for working with timestamps indifferent time zones. By default, pandas objects are time zone unaware:

In [154]: rng = date_range('3/6/2012 00:00', periods=15, freq='D')In [155]: print(rng.tz)None

To supply the time zone, you can use the tz keyword to date_range andother functions:

In [156]: rng_utc = date_range('3/6/2012 00:00', periods=10, freq='D', tz='UTC')In [157]: print(rng_utc.tz)UTC

Timestamps, like Python’s datetime.datetime object can be either time zonenaive or time zone aware. Naive time series and DatetimeIndex objects can belocalized using tz_localize:

In [158]: ts = Series(randn(len(rng)), rng)In [159]: ts_utc = ts.tz_localize('UTC')In [160]: ts_utc2012-03-06 00:00:00+00:00 -2.1892932012-03-07 00:00:00+00:00 -1.8195062012-03-08 00:00:00+00:00 0.2297982012-03-09 00:00:00+00:00 0.1194252012-03-10 00:00:00+00:00 1.8089662012-03-11 00:00:00+00:00 1.0158412012-03-12 00:00:00+00:00 -1.6517842012-03-13 00:00:00+00:00 0.3476742012-03-14 00:00:00+00:00 -0.7736882012-03-15 00:00:00+00:00 0.4258632012-03-16 00:00:00+00:00 0.5794862012-03-17 00:00:00+00:00 -0.7453962012-03-18 00:00:00+00:00 0.1418802012-03-19 00:00:00+00:00 -1.0777542012-03-20 00:00:00+00:00 -1.301174Freq: D, dtype: float64

You can use the tz_convert method to convert pandas objects to converttz-aware data to another time zone:

In [161]: ts_utc.tz_convert('US/Eastern')2012-03-05 19:00:00-05:00 -2.1892932012-03-06 19:00:00-05:00 -1.8195062012-03-07 19:00:00-05:00 0.2297982012-03-08 19:00:00-05:00 0.1194252012-03-09 19:00:00-05:00 1.8089662012-03-10 19:00:00-05:00 1.0158412012-03-11 20:00:00-04:00 -1.6517842012-03-12 20:00:00-04:00 0.3476742012-03-13 20:00:00-04:00 -0.7736882012-03-14 20:00:00-04:00 0.4258632012-03-15 20:00:00-04:00 0.5794862012-03-16 20:00:00-04:00 -0.7453962012-03-17 20:00:00-04:00 0.1418802012-03-18 20:00:00-04:00 -1.0777542012-03-19 20:00:00-04:00 -1.301174Freq: D, dtype: float64

Under the hood, all timestamps are stored in UTC. Scalar values from aDatetimeIndex with a time zone will have their fields (day, hour, minute)localized to the time zone. However, timestamps with the same UTC value arestill considered to be equal even if they are in different time zones:

In [162]: rng_eastern = rng_utc.tz_convert('US/Eastern')In [163]: rng_berlin = rng_utc.tz_convert('Europe/Berlin')In [164]: rng_eastern[5]Timestamp('2012-03-10 19:00:00-0500', tz='US/Eastern')In [165]: rng_berlin[5]Timestamp('2012-03-11 01:00:00+0100', tz='Europe/Berlin')In [166]: rng_eastern[5] == rng_berlin[5]True

Like Series, DataFrame, and DatetimeIndex, Timestamps can be converted to othertime zones using tz_convert:

In [167]: rng_eastern[5]Timestamp('2012-03-10 19:00:00-0500', tz='US/Eastern')In [168]: rng_berlin[5]Timestamp('2012-03-11 01:00:00+0100', tz='Europe/Berlin')In [169]: rng_eastern[5].tz_convert('Europe/Berlin')Timestamp('2012-03-11 01:00:00+0100', tz='Europe/Berlin')

Localization of Timestamps functions just like DatetimeIndex and TimeSeries:

In [170]: rng[5]Timestamp('2012-03-11 00:00:00', tz=None)In [171]: rng[5].tz_localize('Asia/Shanghai')Timestamp('2012-03-11 00:00:00+0800', tz='Asia/Shanghai')

Operations between TimeSeries in difficult time zones will yield UTCTimeSeries, aligning the data on the UTC timestamps:

In [172]: eastern = ts_utc.tz_convert('US/Eastern')In [173]: berlin = ts_utc.tz_convert('Europe/Berlin')In [174]: result = eastern + berlinIn [175]: result2012-03-06 00:00:00+00:00 -4.3785862012-03-07 00:00:00+00:00 -3.6390112012-03-08 00:00:00+00:00 0.4595962012-03-09 00:00:00+00:00 0.2388492012-03-10 00:00:00+00:00 3.6179322012-03-11 00:00:00+00:00 2.0316832012-03-12 00:00:00+00:00 -3.3035682012-03-13 00:00:00+00:00 0.6953492012-03-14 00:00:00+00:00 -1.5473762012-03-15 00:00:00+00:00 0.8517262012-03-16 00:00:00+00:00 1.1589712012-03-17 00:00:00+00:00 -1.4907932012-03-18 00:00:00+00:00 0.2837602012-03-19 00:00:00+00:00 -2.1555082012-03-20 00:00:00+00:00 -2.602348Freq: D, dtype: float64In [176]: result.index<class 'pandas.tseries.index.DatetimeIndex'>[2012-03-06 00:00:00, ..., 2012-03-20 00:00:00]Length: 15, Freq: D, Timezone: UTC

Time Deltas

Timedeltas are differences in times, expressed in difference units, e.g. days,hours,minutes,seconds.They can be both positive and negative.

In [177]: from datetime import datetime, timedeltaIn [178]: s = Series(date_range('2012-1-1', periods=3, freq='D'))In [179]: td = Series([ timedelta(days=i) for i in range(3) ])In [180]: df = DataFrame(dict(A = s, B = td))In [181]: df A B0 2012-01-01 00:00:00 00:00:001 2012-01-02 00:00:00 1 days, 00:00:002 2012-01-03 00:00:00 2 days, 00:00:00In [182]: df['C'] = df['A'] + df['B']In [183]: df A B C0 2012-01-01 00:00:00 00:00:00 2012-01-01 00:00:001 2012-01-02 00:00:00 1 days, 00:00:00 2012-01-03 00:00:002 2012-01-03 00:00:00 2 days, 00:00:00 2012-01-05 00:00:00In [184]: df.dtypesA datetime64[ns]B timedelta64[ns]C datetime64[ns]dtype: objectIn [185]: s - s.max()0 -2 days, 00:00:001 -1 days, 00:00:002 00:00:00dtype: timedelta64[ns]In [186]: s - datetime(2011,1,1,3,5)0 364 days, 20:55:001 365 days, 20:55:002 366 days, 20:55:00dtype: timedelta64[ns]In [187]: s + timedelta(minutes=5)0 2012-01-01 00:05:001 2012-01-02 00:05:002 2012-01-03 00:05:00dtype: datetime64[ns]

Getting scalar results from a timedelta64[ns] series

In [188]: y = s - s[0]In [189]: y0 00:00:001 1 days, 00:00:002 2 days, 00:00:00dtype: timedelta64[ns]
if LooseVersion(np.__version__) <= '1.6.2': y.apply(lambda x: x.item().total_seconds()) y.apply(lambda x: x.item().days)else: y.apply(lambda x: x / np.timedelta64(1, 's')) y.apply(lambda x: x / np.timedelta64(1, 'D'))

Note

As you can see from the conditional statement above, these operations aredifferent in numpy 1.6.2 and in numpy >= 1.7. The timedelta64[ns] scalartype in 1.6.2 is much like a datetime.timedelta, while in 1.7 it is ananosecond based integer. A future version of pandas will make thistransparent.

Note

In numpy >= 1.7 dividing a timedelta64 array by another timedelta64array will yield an array with dtype np.float64.

Series of timedeltas with NaT values are supported

In [190]: y = s - s.shift()In [191]: y0 NaT1 1 days, 00:00:002 1 days, 00:00:00dtype: timedelta64[ns]

Elements can be set to NaT using np.nan analagously to datetimes

In [192]: y[1] = np.nanIn [193]: y0 NaT1 NaT2 1 days, 00:00:00dtype: timedelta64[ns]

Operands can also appear in a reversed order (a singluar object operated with a Series)

In [194]: s.max() - s0 2 days, 00:00:001 1 days, 00:00:002 00:00:00dtype: timedelta64[ns]In [195]: datetime(2011,1,1,3,5) - s0 -364 days, 20:55:001 -365 days, 20:55:002 -366 days, 20:55:00dtype: timedelta64[ns]In [196]: timedelta(minutes=5) + s0 2012-01-01 00:05:001 2012-01-02 00:05:002 2012-01-03 00:05:00dtype: datetime64[ns]

Some timedelta numeric like operations are supported.

In [197]: td - timedelta(minutes=5, seconds=5, microseconds=5)0 -00:05:05.0000051 23:54:54.9999952 1 days, 23:54:54.999995dtype: timedelta64[ns]

min, max and the corresponding idxmin, idxmax operations are supported on frames

In [198]: A = s - Timestamp('20120101') - timedelta(minutes=5, seconds=5)In [199]: B = s - Series(date_range('2012-1-2', periods=3, freq='D'))In [200]: df = DataFrame(dict(A=A, B=B))In [201]: df A B0 -00:05:05 -1 days, 00:00:001 23:54:55 -1 days, 00:00:002 1 days, 23:54:55 -1 days, 00:00:00In [202]: df.min()A -00:05:05B -1 days, 00:00:00dtype: timedelta64[ns]In [203]: df.min(axis=1)0 -1 days, 00:00:001 -1 days, 00:00:002 -1 days, 00:00:00dtype: timedelta64[ns]In [204]: df.idxmin()A 0B 0dtype: int64In [205]: df.idxmax()A 2B 0dtype: int64

min, max operations are supported on series; these return a single elementtimedelta64[ns] Series (this avoids having to deal with numpy timedelta64issues). idxmin, idxmax are supported as well.

In [206]: df.min().max()0 -00:05:05dtype: timedelta64[ns]In [207]: df.min(axis=1).min()0 -1 days, 00:00:00dtype: timedelta64[ns]In [208]: df.min().idxmax()'A'In [209]: df.min(axis=1).idxmin()0
Time Series / Date functionality — pandas 0.12.0 documentation (2024)
Top Articles
Latest Posts
Article information

Author: Tuan Roob DDS

Last Updated:

Views: 6011

Rating: 4.1 / 5 (62 voted)

Reviews: 85% of readers found this page helpful

Author information

Name: Tuan Roob DDS

Birthday: 1999-11-20

Address: Suite 592 642 Pfannerstill Island, South Keila, LA 74970-3076

Phone: +9617721773649

Job: Marketing Producer

Hobby: Skydiving, Flag Football, Knitting, Running, Lego building, Hunting, Juggling

Introduction: My name is Tuan Roob DDS, I am a friendly, good, energetic, faithful, fantastic, gentle, enchanting person who loves writing and wants to share my knowledge and understanding with you.