Repository: bokeh/bokeh

Issue:
Support stream and update with pandas dataframes
When calling `ColumnDataSource.update` or `.stream` with Pandas dataframes I get JSON serialization errors

```python
source.stream(df)  # this fails
```

I find that I need to explicitly unpack the arrays beforehand

```python
d = {c: df[c] for c in df.columns}
source.stream(d)
```

Interestingly passing dataframes does work on the first iteration, but fails on every subsequent iteration.


Discussion:
`ColumnDataSource.stream(new_data)` accepts only dictionaries. I presume support for `DataFrame` could be added the same way as it's handled in `ColumnDataSource`'s constructor.
@mattpap Actually I've implemented this functionality, but I think it has been broken by `a57cbf3` which started sending data along with events.
@p-himik can you fix the link to the commit? I would like to look at what changed
@p-himik  I guess you mean https://github.com/bokeh/bokeh/commit/a57cbf369bbc3d1e3d8f3d1b50887118c86ec031 ? I don't offhand see anything in that PR that seems like it should affect things, but perhaps you can draw my attention to something I missed. 

@mrocklin can you provide an MRE for testing?
```python
from tornado.ioloop import IOLoop

import pandas as pd

from bokeh.server.server import Server
from bokeh.plotting import ColumnDataSource, figure

df = pd.DataFrame({'x': [1, 2, 3], 'y': [4, 5, 6]})


def main_page(doc):
    source = ColumnDataSource({'x': [], 'y': []})
    fig = figure(title='figure')
    fig.line(source=source, x='x', y='y')

    def f():
        source.stream(df, 100)

    doc.add_periodic_callback(f, 100)
    doc.add_root(fig)

server = Server({'/main': main_page})
server.start()

IOLoop.current().start()
```
OK @p-himik I understand your point, this has to do with the addition of the `generate` code path to let events be responsible for their own serialization. There's probably a few possible ways to tackle this but I will try to have a PR this afternoon (with more tests)
Here is an immediate fix
```
diff --git a/bokeh/models/sources.py b/bokeh/models/sources.py
index df9d22e6b..d1a6f709f 100644
--- a/bokeh/models/sources.py
+++ b/bokeh/models/sources.py
@@ -396,6 +396,7 @@ class ColumnDataSource(ColumnarDataSource):
             new_data = new_data.to_frame().T
         if pd and isinstance(new_data, pd.DataFrame):
             newkeys = set(new_data.columns)
+            new_data = {c: new_data[c] for c in new_data.columns}
         else:
             newkeys = set(new_data.keys())
         oldkeys = set(self.data.keys())
```
Another route would be to make `ColumnsStreamedEvent` more flexible and have accept date frames and do the necessary conversion. I'm not sure which I'd favor. I like the idea of keeping he interface to the event very simple (always a dict) but putting the conversion in the event would probably be easier to isolate and test. 
This is the other solution:
```
diff --git a/bokeh/document/events.py b/bokeh/document/events.py
index 6069898fe..227b1ea40 100644
--- a/bokeh/document/events.py
+++ b/bokeh/document/events.py
@@ -7,6 +7,10 @@ for :ref:`bokeh.events`.
 '''
 from __future__ import absolute_import

+from ..util.dependencies import import_optional
+
+pd = import_optional('pandas')
+
 class DocumentChangedEvent(object):
     ''' Base class for all internal events representing a change to a
     Bokeh Document.
@@ -369,6 +373,10 @@ class ColumnsStreamedEvent(DocumentPatchedEvent):
         '''
         super(ColumnsStreamedEvent, self).__init__(document, setter, callback_invoker)
         self.column_source = column_source
+
+        if pd and isinstance(data, pd.DataFrame):
+            data = {c: data[c] for c in data.columns}
+
         self.data = data
         self.rollover = rollover
```
I am going to go with this for ease of testing. 

Resolving patch:
diff --git a/bokeh/document/events.py b/bokeh/document/events.py
--- a/bokeh/document/events.py
+++ b/bokeh/document/events.py
@@ -7,6 +7,10 @@
 '''
 from __future__ import absolute_import
 
+from ..util.dependencies import import_optional
+
+pd = import_optional('pandas')
+
 class DocumentChangedEvent(object):
     ''' Base class for all internal events representing a change to a
     Bokeh Document.
@@ -346,7 +350,10 @@ def __init__(self, document, column_source, data, rollover, setter=None, callbac
             column_source (ColumnDataSource) :
                 The data source to stream new data to.
 
-            data (dict) :
+            data (dict or DataFrame) :
+                New data to stream.
+
+                If a DataFrame, will be stored as ``{c: df[c] for c in df.columns}``
 
             rollover (int) :
                 A rollover limit. If the data source columns exceed this
@@ -365,10 +372,13 @@ def __init__(self, document, column_source, data, rollover, setter=None, callbac
                 be executed in response to the change that triggered this
                 event. (default: None)
 
-
         '''
         super(ColumnsStreamedEvent, self).__init__(document, setter, callback_invoker)
         self.column_source = column_source
+
+        if pd and isinstance(data, pd.DataFrame):
+            data = {c: data[c] for c in data.columns}
+
         self.data = data
         self.rollover = rollover
 


Test patch:
diff --git a/bokeh/document/tests/test_events.py b/bokeh/document/tests/test_events.py
--- a/bokeh/document/tests/test_events.py
+++ b/bokeh/document/tests/test_events.py
@@ -1,6 +1,7 @@
 import pytest
 
 from mock import patch
+import pandas as pd
 
 import bokeh.document.events as bde
 
@@ -269,6 +270,13 @@ def test_combine_ignores_all(self):
         assert e.data == dict(foo=1)
         assert e.rollover == 200
 
+    def test_pandas_data(self):
+        m = FakeModel()
+        df = pd.DataFrame({'x': [1, 2, 3], 'y': [4, 5, 6]})
+        e = bde.ColumnsStreamedEvent("doc", m, df, 200, "setter", "invoker")
+        assert isinstance(e.data, dict)
+        assert e.data == {c: df[c] for c in df.columns}
+
 # ColumnsPatchedEvent ---------------------------------------------------------
 
 class TestColumnsPatchedEvent(object):