Skip to content

Commit 0b679b4

Browse files
committed
ENH: StringMethods now supports ljust and rjust
1 parent 224a66d commit 0b679b4

File tree

5 files changed

+152
-30
lines changed

5 files changed

+152
-30
lines changed

doc/source/api.rst

+2
Original file line numberDiff line numberDiff line change
@@ -535,12 +535,14 @@ strings and apply several methods to it. These can be acccessed like
535535
Series.str.get
536536
Series.str.join
537537
Series.str.len
538+
Series.str.ljust
538539
Series.str.lower
539540
Series.str.lstrip
540541
Series.str.match
541542
Series.str.pad
542543
Series.str.repeat
543544
Series.str.replace
545+
Series.str.rjust
544546
Series.str.rstrip
545547
Series.str.slice
546548
Series.str.slice_replace

doc/source/text.rst

+3-1
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,9 @@ Method Summary
212212
:meth:`~Series.str.replace`,Replace occurrences of pattern/regex with some other string
213213
:meth:`~Series.str.repeat`,Duplicate values (``s.str.repeat(3)`` equivalent to ``x * 3``)
214214
:meth:`~Series.str.pad`,"Add whitespace to left, right, or both sides of strings"
215-
:meth:`~Series.str.center`,Equivalent to ``pad(side='both')``
215+
:meth:`~Series.str.center`,Equivalent to ``str.center``
216+
:meth:`~Series.str.ljust`,Equivalent to ``str.ljust``
217+
:meth:`~Series.str.rjust`,Equivalent to ``str.rjust``
216218
:meth:`~Series.str.wrap`,Split long strings into lines with length less than a given width
217219
:meth:`~Series.str.slice`,Slice each string in the Series
218220
:meth:`~Series.str.slice_replace`,Replace slice in each string with passed value

doc/source/whatsnew/v0.16.0.txt

+5
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,11 @@ Enhancements
110110

111111
- Added auto-complete for ``Series.str.<tab>``, ``Series.dt.<tab>`` and ``Series.cat.<tab>`` (:issue:`9322`)
112112

113+
114+
115+
- Added ``StringMethods.ljust()`` and ``rjust()`` which behave as the same as standard ``str`` (:issue:`9352`)
116+
- ``StringMethods.pad()`` and ``center()`` now accept `fillchar` option to specify filling character (:issue:`9352`)
117+
113118
Performance
114119
~~~~~~~~~~~
115120

pandas/core/strings.py

+48-28
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,16 @@
33
from pandas.compat import zip
44
from pandas.core.common import isnull, _values_from_object
55
import pandas.compat as compat
6+
from pandas.util.decorators import Appender
67
import re
78
import pandas.lib as lib
89
import warnings
910
import textwrap
1011

1112

13+
_shared_docs = dict()
14+
15+
1216
def _get_array_list(arr, others):
1317
from pandas.core.series import Series
1418

@@ -583,9 +587,9 @@ def str_findall(arr, pat, flags=0):
583587
return _na_map(regex.findall, arr)
584588

585589

586-
def str_pad(arr, width, side='left'):
590+
def str_pad(arr, width, side='left', fillchar=' '):
587591
"""
588-
Pad strings with whitespace
592+
Pad strings with an additional character
589593
590594
Parameters
591595
----------
@@ -594,40 +598,33 @@ def str_pad(arr, width, side='left'):
594598
Minimum width of resulting string; additional characters will be filled
595599
with spaces
596600
side : {'left', 'right', 'both'}, default 'left'
601+
fillchar : str
602+
Additional character for filling, default is whitespace
597603
598604
Returns
599605
-------
600606
padded : array
601607
"""
608+
609+
if not isinstance(fillchar, compat.string_types):
610+
msg = 'fillchar must be a character, not {0}'
611+
raise TypeError(msg.format(type(fillchar).__name__))
612+
613+
if len(fillchar) != 1:
614+
raise TypeError('fillchar must be a character, not str')
615+
602616
if side == 'left':
603-
f = lambda x: x.rjust(width)
617+
f = lambda x: x.rjust(width, fillchar)
604618
elif side == 'right':
605-
f = lambda x: x.ljust(width)
619+
f = lambda x: x.ljust(width, fillchar)
606620
elif side == 'both':
607-
f = lambda x: x.center(width)
621+
f = lambda x: x.center(width, fillchar)
608622
else: # pragma: no cover
609623
raise ValueError('Invalid side')
610624

611625
return _na_map(f, arr)
612626

613627

614-
def str_center(arr, width):
615-
"""
616-
"Center" strings, filling left and right side with additional whitespace
617-
618-
Parameters
619-
----------
620-
width : int
621-
Minimum width of resulting string; additional characters will be filled
622-
with spaces
623-
624-
Returns
625-
-------
626-
centered : array
627-
"""
628-
return str_pad(arr, width, side='both')
629-
630-
631628
def str_split(arr, pat=None, n=None, return_type='series'):
632629
"""
633630
Split each string (a la re.split) in array by given pattern, propagating NA
@@ -1016,14 +1013,37 @@ def repeat(self, repeats):
10161013
return self._wrap_result(result)
10171014

10181015
@copy(str_pad)
1019-
def pad(self, width, side='left'):
1020-
result = str_pad(self.series, width, side=side)
1016+
def pad(self, width, side='left', fillchar=' '):
1017+
result = str_pad(self.series, width, side=side, fillchar=fillchar)
10211018
return self._wrap_result(result)
10221019

1023-
@copy(str_center)
1024-
def center(self, width):
1025-
result = str_center(self.series, width)
1026-
return self._wrap_result(result)
1020+
_shared_docs['str_pad'] = ("""
1021+
"Center" strings, filling %s side with an additional character
1022+
1023+
Parameters
1024+
----------
1025+
width : int
1026+
Minimum width of resulting string; additional characters will be filled
1027+
with ``fillchar``
1028+
fillchar : str
1029+
Additional character for filling, default is whitespace
1030+
1031+
Returns
1032+
-------
1033+
centered : array
1034+
""")
1035+
1036+
@Appender(_shared_docs['str_pad'] % 'left and right')
1037+
def center(self, width, fillchar=' '):
1038+
return self.pad(width, side='both', fillchar=fillchar)
1039+
1040+
@Appender(_shared_docs['str_pad'] % 'right')
1041+
def ljust(self, width, fillchar=' '):
1042+
return self.pad(width, side='right', fillchar=fillchar)
1043+
1044+
@Appender(_shared_docs['str_pad'] % 'left')
1045+
def rjust(self, width, fillchar=' '):
1046+
return self.pad(width, side='left', fillchar=fillchar)
10271047

10281048
@copy(str_slice)
10291049
def slice(self, start=None, stop=None, step=None):

pandas/tests/test_strings.py

+94-1
Original file line numberDiff line numberDiff line change
@@ -770,21 +770,62 @@ def test_pad(self):
770770
u('eeeeee')])
771771
tm.assert_almost_equal(result, exp)
772772

773-
def test_center(self):
773+
def test_pad_fillchar(self):
774+
775+
values = Series(['a', 'b', NA, 'c', NA, 'eeeeee'])
776+
777+
result = values.str.pad(5, side='left', fillchar='X')
778+
exp = Series(['XXXXa', 'XXXXb', NA, 'XXXXc', NA, 'eeeeee'])
779+
tm.assert_almost_equal(result, exp)
780+
781+
result = values.str.pad(5, side='right', fillchar='X')
782+
exp = Series(['aXXXX', 'bXXXX', NA, 'cXXXX', NA, 'eeeeee'])
783+
tm.assert_almost_equal(result, exp)
784+
785+
result = values.str.pad(5, side='both', fillchar='X')
786+
exp = Series(['XXaXX', 'XXbXX', NA, 'XXcXX', NA, 'eeeeee'])
787+
tm.assert_almost_equal(result, exp)
788+
789+
with tm.assertRaisesRegexp(TypeError, "fillchar must be a character, not str"):
790+
result = values.str.pad(5, fillchar='XY')
791+
792+
with tm.assertRaisesRegexp(TypeError, "fillchar must be a character, not int"):
793+
result = values.str.pad(5, fillchar=5)
794+
795+
def test_center_ljust_rjust(self):
774796
values = Series(['a', 'b', NA, 'c', NA, 'eeeeee'])
775797

776798
result = values.str.center(5)
777799
exp = Series([' a ', ' b ', NA, ' c ', NA, 'eeeeee'])
778800
tm.assert_almost_equal(result, exp)
779801

802+
result = values.str.ljust(5)
803+
exp = Series(['a ', 'b ', NA, 'c ', NA, 'eeeeee'])
804+
tm.assert_almost_equal(result, exp)
805+
806+
result = values.str.rjust(5)
807+
exp = Series([' a', ' b', NA, ' c', NA, 'eeeeee'])
808+
tm.assert_almost_equal(result, exp)
809+
780810
# mixed
781811
mixed = Series(['a', NA, 'b', True, datetime.today(),
782812
'c', 'eee', None, 1, 2.])
783813

784814
rs = Series(mixed).str.center(5)
785815
xp = Series([' a ', NA, ' b ', NA, NA, ' c ', ' eee ', NA, NA,
786816
NA])
817+
tm.assert_isinstance(rs, Series)
818+
tm.assert_almost_equal(rs, xp)
787819

820+
rs = Series(mixed).str.ljust(5)
821+
xp = Series(['a ', NA, 'b ', NA, NA, 'c ', 'eee ', NA, NA,
822+
NA])
823+
tm.assert_isinstance(rs, Series)
824+
tm.assert_almost_equal(rs, xp)
825+
826+
rs = Series(mixed).str.rjust(5)
827+
xp = Series([' a', NA, ' b', NA, NA, ' c', ' eee', NA, NA,
828+
NA])
788829
tm.assert_isinstance(rs, Series)
789830
tm.assert_almost_equal(rs, xp)
790831

@@ -797,6 +838,58 @@ def test_center(self):
797838
u('eeeeee')])
798839
tm.assert_almost_equal(result, exp)
799840

841+
result = values.str.ljust(5)
842+
exp = Series([u('a '), u('b '), NA, u('c '), NA,
843+
u('eeeeee')])
844+
tm.assert_almost_equal(result, exp)
845+
846+
result = values.str.rjust(5)
847+
exp = Series([u(' a'), u(' b'), NA, u(' c'), NA,
848+
u('eeeeee')])
849+
tm.assert_almost_equal(result, exp)
850+
851+
def test_center_ljust_rjust_fillchar(self):
852+
values = Series(['a', 'bb', 'cccc', 'ddddd', 'eeeeee'])
853+
854+
result = values.str.center(5, fillchar='X')
855+
expected = Series(['XXaXX', 'XXbbX', 'Xcccc', 'ddddd', 'eeeeee'])
856+
tm.assert_series_equal(result, expected)
857+
expected = np.array([v.center(5, 'X') for v in values.values])
858+
tm.assert_numpy_array_equal(result.values, expected)
859+
860+
result = values.str.ljust(5, fillchar='X')
861+
expected = Series(['aXXXX', 'bbXXX', 'ccccX', 'ddddd', 'eeeeee'])
862+
tm.assert_series_equal(result, expected)
863+
expected = np.array([v.ljust(5, 'X') for v in values.values])
864+
tm.assert_numpy_array_equal(result.values, expected)
865+
866+
result = values.str.rjust(5, fillchar='X')
867+
expected = Series(['XXXXa', 'XXXbb', 'Xcccc', 'ddddd', 'eeeeee'])
868+
tm.assert_series_equal(result, expected)
869+
expected = np.array([v.rjust(5, 'X') for v in values.values])
870+
tm.assert_numpy_array_equal(result.values, expected)
871+
872+
# If fillchar is not a charatter, normal str raises TypeError
873+
# 'aaa'.ljust(5, 'XY')
874+
# TypeError: must be char, not str
875+
with tm.assertRaisesRegexp(TypeError, "fillchar must be a character, not str"):
876+
result = values.str.center(5, fillchar='XY')
877+
878+
with tm.assertRaisesRegexp(TypeError, "fillchar must be a character, not str"):
879+
result = values.str.ljust(5, fillchar='XY')
880+
881+
with tm.assertRaisesRegexp(TypeError, "fillchar must be a character, not str"):
882+
result = values.str.rjust(5, fillchar='XY')
883+
884+
with tm.assertRaisesRegexp(TypeError, "fillchar must be a character, not int"):
885+
result = values.str.center(5, fillchar=1)
886+
887+
with tm.assertRaisesRegexp(TypeError, "fillchar must be a character, not int"):
888+
result = values.str.ljust(5, fillchar=1)
889+
890+
with tm.assertRaisesRegexp(TypeError, "fillchar must be a character, not int"):
891+
result = values.str.rjust(5, fillchar=1)
892+
800893
def test_split(self):
801894
values = Series(['a_b_c', 'c_d_e', NA, 'f_g_h'])
802895

0 commit comments

Comments
 (0)