20
20
from warnings import warn
21
21
22
22
from ..._async_compat .util import AsyncUtil
23
- from ...data import DataDehydrator
23
+ from ...data import (
24
+ DataDehydrator ,
25
+ RecordTableRowExporter ,
26
+ )
24
27
from ...exceptions import (
25
28
ResultConsumedError ,
26
29
ResultNotSingleError ,
@@ -524,15 +527,74 @@ async def data(self, *keys):
524
527
525
528
@experimental ("pandas support is experimental and might be changed or "
526
529
"removed in future versions" )
527
- async def to_df (self ):
528
- """Convert (the rest of) the result to a pandas DataFrame.
530
+ async def to_df (self , * keys , expand = False ):
531
+ r """Convert (the rest of) the result to a pandas DataFrame.
529
532
530
533
This method is only available if the `pandas` library is installed.
531
534
532
535
``tx.run("UNWIND range(1, 10) AS n RETURN n, n+1 as m").to_df()``, for
533
536
instance will return a DataFrame with two columns: ``n`` and ``m`` and
534
537
10 rows.
535
538
539
+ :param expand: if :const:`True`, some structures in the result will be
540
+ recursively expanded (flattened out into multiple columns) like so:
541
+
542
+ * :class:`.Node` objects under any variable ``<n>`` will be
543
+ expanded into columns (the recursion stops here)
544
+
545
+ * ``<n>().prop.<property_name>`` (any) for each property of the
546
+ node.
547
+ * ``<n>().element_id`` (str) the node's element id.
548
+ See :attr:`.Node.element_id`.
549
+ * ``<n>().labels`` (list of str) the node's labels.
550
+ See :attr:`.Node.labels`.
551
+
552
+ * :class:`.Relationship` objects under any variable ``<r>``
553
+ will be expanded into columns (the recursion stops here)
554
+
555
+ * ``<r>->.prop.<property_name>`` (any) for each property of the
556
+ relationship.
557
+ * ``<r>->.element_id`` (str) the relationship's element id.
558
+ See :attr:`.Relationship.element_id`.
559
+ * ``<r>->.start.element_id`` (str) the relationship's
560
+ start node's element id.
561
+ See :attr:`.Relationship.start_node`.
562
+ * ``<r>->.end.element_id`` (str) the relationship's
563
+ end node's element id.
564
+ See :attr:`.Relationship.end_node`.
565
+ * ``<r>->.type`` (str) the relationship's type.
566
+ See :attr:`.Relationship.type`.
567
+
568
+ * :const:`list` objects under any variable ``<l>`` will be expanded
569
+ into
570
+
571
+ * ``<l>[].0`` (any) the 1st list element
572
+ * ``<l>[].1`` (any) the 2nd list element
573
+ * ...
574
+
575
+ * :const:`dict` objects under any variable ``<d>`` will be expanded
576
+ into
577
+
578
+ * ``<d>{}.<key1>`` (any) the 1st key of the dict
579
+ * ``<d>{}.<key2>`` (any) the 2nd key of the dict
580
+ * ...
581
+
582
+ * :const:`list` and :const:`dict` objects are expanded recursively.
583
+ Example::
584
+
585
+ [{"foo": "bar", "baz": [42, 0]}, "foobar"]
586
+
587
+ will be expanded to::
588
+
589
+ {"0.foo": "bar", "0.baz.0": 42, "0.baz.1": 0, "1": "foobar"}
590
+
591
+ * Everything else (including :class:`.Path` objects) will not
592
+ be flattened.
593
+
594
+ :const:`dict` keys and variable names that contain ``.`` or ``\``
595
+ will be escaped with a backslash (``\.`` and ``\\`` respectively).
596
+ :type expand: bool
597
+
536
598
:rtype: :py:class:`pandas.DataFrame`
537
599
:raises ImportError: if `pandas` library is not available.
538
600
:raises ResultConsumedError: if the transaction from which this result
@@ -545,7 +607,33 @@ async def to_df(self):
545
607
"""
546
608
import pandas as pd
547
609
548
- return pd .DataFrame (await self .values (), columns = self ._keys )
610
+ if not expand :
611
+ return pd .DataFrame (await self .values (* keys ),
612
+ columns = keys or self ._keys )
613
+ else :
614
+ df_keys = None
615
+ rows = []
616
+ async for record in self :
617
+ row = RecordTableRowExporter ().transform (
618
+ dict (record .items (* keys ))
619
+ )
620
+ if df_keys == row .keys ():
621
+ rows .append (row .values ())
622
+ elif df_keys is None :
623
+ df_keys = row .keys ()
624
+ rows .append (row .values ())
625
+ elif df_keys is False :
626
+ rows .append (row )
627
+ else :
628
+ # The rows have different keys. We need to pass a list
629
+ # of dicts to pandas
630
+ rows = [{k : v for k , v in zip (df_keys , r )} for r in rows ]
631
+ df_keys = False
632
+ rows .append (row )
633
+ if df_keys is False :
634
+ return pd .DataFrame (rows )
635
+ else :
636
+ return pd .DataFrame (rows , columns = df_keys or self ._keys )
549
637
550
638
def closed (self ):
551
639
"""Return True if the result has been closed.
0 commit comments