Repository: vega/altair

Issue:
pd.Timedelta is not JSON serializable
In:
```python
td =  pd.timedelta_range(0,periods=3,freq='h')
alt.Chart(
    pd.DataFrame(dict(id=np.arange(td.size),timedelta = td))
).mark_bar(
).encode(
 x = 'timedelta',
 y ='id:O'
)
```
Out:
```python
~/anaconda/anaconda/envs/rr_dev/lib/python3.5/json/encoder.py in default(self, o)
    177 
    178         """
--> 179         raise TypeError(repr(o) + " is not JSON serializable")
    180 
    181     def encode(self, o):

TypeError: Timedelta('0 days 00:00:00') is not JSON serializable
```
In:
```python
print ('pandas',pd.__version__)
print ('altair',alt.__version__)
```
Out:

```python
pandas 0.23.0
altair 2.1.0
```
Expected, but I'm not sure that is  achievable without `timeUnit="hoursminutes"`.
![visualization 17](https://user-images.githubusercontent.com/11561581/41821223-01e72d06-77e6-11e8-9850-b8d61faad284.png)






Discussion:
Vega only supports time stamps, not time deltas. The best apporach to time deltas is to encode your data as a timestamp and extract the part of the date you want with a timeunit. For example:
```python
import altair as alt
import pandas as pd
import numpy as np

td =  pd.timedelta_range(0,periods=3,freq='h')
timestamp = pd.to_datetime('2018-01-01') + td

df = pd.DataFrame(dict(id=np.arange(td.size), time=timestamp))

alt.Chart(df).mark_bar().encode(
    x='hoursminutes(time)',
    y='id:O'
)
```
![visualization 19](https://user-images.githubusercontent.com/781659/41821886-19dd75ea-779c-11e8-8ef8-23bc1c29fb2f.png)

IMHO, will be great in that case at least throw `NotImplemented` with your answer it's more understandable than JSON error. 
Or just do what you propose while sanitizing DataFrame and parsing shorthand. It will involve some magic to understand what is appropriate precision for timedelta but field maximum value will give us that information.
I don't think it's appropriate to automatically convert timedeltas to time stamps, because it would be quite confusing. A different error might help, though. What would you suggest?
In my case I came to converting it to hours as float. That worked better for stacked bar chart displaying how daily hours was spent.  I do not have enough experience with that type to propose some reasonable conversion.  But `"is not JSON serializable"` works for me like a red rag on a bull. I read it as altair is broken again :) 

I propose throwing `NotImplementedException` with text something like : `Field {fieldName} has type {fieldType} that has no representation in vega-lite. Drop the field before passing DataFrame to Altair or convert to other type. `

Or just to **drop fields with known unsupported types with same warning** to user that may be useful when you get wide DataFrame from unknown source and exploring what you just get. In that case the need for writing extra code just for cleaning DataFrame before first look annoying. I think it is the most useful way to solve a problem because if you use that field in a chart it would not work anyway but if you don't you will get a result without any efforts and thats cool.
OK, I've added a better error in #974
For what it's worth, the ``"is not JSON serializable"`` error comes from JSON, not from Altair, so the only way we get around that is to anticipate what types the user might pass to Altair and catch those errors earlier.

Resolving patch:
diff --git a/altair/utils/core.py b/altair/utils/core.py
--- a/altair/utils/core.py
+++ b/altair/utils/core.py
@@ -97,6 +97,7 @@ def sanitize_dataframe(df):
     * Convert np.int dtypes to Python int objects
     * Convert floats to objects and replace NaNs/infs with None.
     * Convert DateTime dtypes into appropriate string representations
+    * Raise a ValueError for TimeDelta dtypes
     """
     df = df.copy()
 
@@ -124,6 +125,11 @@ def to_list_if_array(val):
             # Convert datetimes to strings
             # astype(str) will choose the appropriate resolution
             df[col_name] = df[col_name].astype(str).replace('NaT', '')
+        elif str(dtype).startswith('timedelta'):
+            raise ValueError('Field "{col_name}" has type "{dtype}" which is '
+                             'not supported by Altair. Please convert to '
+                             'either a timestamp or a numerical value.'
+                             ''.format(col_name=col_name, dtype=dtype))
         elif np.issubdtype(dtype, np.integer):
             # convert integers to objects; np.int is not JSON serializable
             df[col_name] = df[col_name].astype(object)


Test patch:
diff --git a/altair/utils/tests/test_utils.py b/altair/utils/tests/test_utils.py
--- a/altair/utils/tests/test_utils.py
+++ b/altair/utils/tests/test_utils.py
@@ -80,6 +80,13 @@ def test_sanitize_dataframe():
     assert df.equals(df2)
 
 
+def test_sanitize_dataframe_timedelta():
+    df = pd.DataFrame({'r': pd.timedelta_range(start='1 day', periods=4)})
+    with pytest.raises(ValueError) as err:
+        sanitize_dataframe(df)
+    assert str(err.value).startswith('Field "r" has type "timedelta')
+
+
 def test_sanitize_dataframe_infs():
     df = pd.DataFrame({'x': [0, 1, 2, np.inf, -np.inf, np.nan]})
     df_clean = sanitize_dataframe(df)