Skip to content

Commit 37a80bc

Browse files
committed
Merge pull request #11416 from SixtyCapital/namedtuple-fields-as-columns
ENH: namedtuple's fields as columns
2 parents 9a6de4e + 0e1da54 commit 37a80bc

File tree

5 files changed

+32
-13
lines changed

5 files changed

+32
-13
lines changed

doc/source/whatsnew/v0.17.1.txt

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ Other Enhancements
3030

3131
- ``pd.read_*`` functions can now also accept :class:`python:pathlib.Path`, or :class:`py:py._path.local.LocalPath`
3232
objects for the ``filepath_or_buffer`` argument. (:issue:`11033`)
33+
- ``DataFrame`` now uses the fields of a ``namedtuple`` as columns, if columns are not supplied (:issue:`11181`)
3334
- Improve the error message displayed in :func:`pandas.io.gbq.to_gbq` when the DataFrame does not match the schema of the destination table (:issue:`11359`)
3435

3536
.. _whatsnew_0171.api:

pandas/core/common.py

+3
Original file line numberDiff line numberDiff line change
@@ -2676,6 +2676,9 @@ def is_list_like(arg):
26762676
return (hasattr(arg, '__iter__') and
26772677
not isinstance(arg, compat.string_and_binary_types))
26782678

2679+
def is_named_tuple(arg):
2680+
return isinstance(arg, tuple) and hasattr(arg, '_fields')
2681+
26792682
def is_null_slice(obj):
26802683
""" we have a null slice """
26812684
return (isinstance(obj, slice) and obj.start is None and

pandas/core/frame.py

+2
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,8 @@ def __init__(self, data=None, index=None, columns=None, dtype=None,
261261
data = list(data)
262262
if len(data) > 0:
263263
if is_list_like(data[0]) and getattr(data[0], 'ndim', 1) == 1:
264+
if com.is_named_tuple(data[0]) and columns is None:
265+
columns = data[0]._fields
264266
arrays, columns = _to_arrays(data, columns, dtype=dtype)
265267
columns = _ensure_index(columns)
266268

pandas/tests/test_common.py

+9
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,15 @@ def test_is_list_like():
538538
for f in fails:
539539
assert not com.is_list_like(f)
540540

541+
def test_is_named_tuple():
542+
passes = (collections.namedtuple('Test',list('abc'))(1,2,3),)
543+
fails = ((1,2,3), 'a', Series({'pi':3.14}))
544+
545+
for p in passes:
546+
assert com.is_named_tuple(p)
547+
548+
for f in fails:
549+
assert not com.is_named_tuple(f)
541550

542551
def test_is_hashable():
543552

pandas/tests/test_frame.py

+17-13
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@
1616

1717
from pandas.compat import(
1818
map, zip, range, long, lrange, lmap, lzip,
19-
OrderedDict, u, StringIO, string_types,
20-
is_platform_windows
19+
OrderedDict, u, StringIO, is_platform_windows
2120
)
2221
from pandas import compat
2322

@@ -33,8 +32,7 @@
3332
import pandas.core.datetools as datetools
3433
from pandas import (DataFrame, Index, Series, Panel, notnull, isnull,
3534
MultiIndex, DatetimeIndex, Timestamp, date_range,
36-
read_csv, timedelta_range, Timedelta, CategoricalIndex,
37-
option_context, period_range)
35+
read_csv, timedelta_range, Timedelta, option_context, period_range)
3836
from pandas.core.dtypes import DatetimeTZDtype
3937
import pandas as pd
4038
from pandas.parser import CParserError
@@ -2239,7 +2237,6 @@ class TestDataFrame(tm.TestCase, CheckIndexing,
22392237
_multiprocess_can_split_ = True
22402238

22412239
def setUp(self):
2242-
import warnings
22432240

22442241
self.frame = _frame.copy()
22452242
self.frame2 = _frame2.copy()
@@ -3568,6 +3565,20 @@ def test_constructor_tuples(self):
35683565
expected = DataFrame({'A': Series([(1, 2), (3, 4)])})
35693566
assert_frame_equal(result, expected)
35703567

3568+
def test_constructor_namedtuples(self):
3569+
# GH11181
3570+
from collections import namedtuple
3571+
named_tuple = namedtuple("Pandas", list('ab'))
3572+
tuples = [named_tuple(1, 3), named_tuple(2, 4)]
3573+
expected = DataFrame({'a': [1, 2], 'b': [3, 4]})
3574+
result = DataFrame(tuples)
3575+
assert_frame_equal(result, expected)
3576+
3577+
# with columns
3578+
expected = DataFrame({'y': [1, 2], 'z': [3, 4]})
3579+
result = DataFrame(tuples, columns=['y', 'z'])
3580+
assert_frame_equal(result, expected)
3581+
35713582
def test_constructor_orient(self):
35723583
data_dict = self.mixed_frame.T._series
35733584
recons = DataFrame.from_dict(data_dict, orient='index')
@@ -4418,7 +4429,7 @@ def test_timedeltas(self):
44184429

44194430
def test_operators_timedelta64(self):
44204431

4421-
from datetime import datetime, timedelta
4432+
from datetime import timedelta
44224433
df = DataFrame(dict(A = date_range('2012-1-1', periods=3, freq='D'),
44234434
B = date_range('2012-1-2', periods=3, freq='D'),
44244435
C = Timestamp('20120101')-timedelta(minutes=5,seconds=5)))
@@ -9645,7 +9656,6 @@ def test_replace_mixed(self):
96459656
assert_frame_equal(result,expected)
96469657

96479658
# test case from
9648-
from pandas.util.testing import makeCustomDataframe as mkdf
96499659
df = DataFrame({'A' : Series([3,0],dtype='int64'), 'B' : Series([0,3],dtype='int64') })
96509660
result = df.replace(3, df.mean().to_dict())
96519661
expected = df.copy().astype('float64')
@@ -12227,7 +12237,6 @@ def test_sort_index_inplace(self):
1222712237
assert_frame_equal(df, expected)
1222812238

1222912239
def test_sort_index_different_sortorder(self):
12230-
import random
1223112240
A = np.arange(20).repeat(5)
1223212241
B = np.tile(np.arange(5), 20)
1223312242

@@ -13301,7 +13310,6 @@ def test_quantile(self):
1330113310

1330213311
def test_quantile_axis_parameter(self):
1330313312
# GH 9543/9544
13304-
from numpy import percentile
1330513313

1330613314
df = DataFrame({"A": [1, 2, 3], "B": [2, 3, 4]}, index=[1, 2, 3])
1330713315

@@ -16093,8 +16101,6 @@ def test_query_doesnt_pickup_local(self):
1609316101
n = m = 10
1609416102
df = DataFrame(np.random.randint(m, size=(n, 3)), columns=list('abc'))
1609516103

16096-
from numpy import sin
16097-
1609816104
# we don't pick up the local 'sin'
1609916105
with tm.assertRaises(UndefinedVariableError):
1610016106
df.query('sin > 5', engine=engine, parser=parser)
@@ -16392,7 +16398,6 @@ def setUpClass(cls):
1639216398
cls.frame = _frame.copy()
1639316399

1639416400
def test_query_builtin(self):
16395-
from pandas.computation.engines import NumExprClobberingError
1639616401
engine, parser = self.engine, self.parser
1639716402

1639816403
n = m = 10
@@ -16413,7 +16418,6 @@ def setUpClass(cls):
1641316418
cls.frame = _frame.copy()
1641416419

1641516420
def test_query_builtin(self):
16416-
from pandas.computation.engines import NumExprClobberingError
1641716421
engine, parser = self.engine, self.parser
1641816422

1641916423
n = m = 10

0 commit comments

Comments
 (0)