R의 magritrr의 %> %와 같은 파이썬의 기능성 파이프


84

R에서 (덕분에 magritrr) 이제를 통해 더 기능적인 파이핑 구문으로 작업을 수행 할 수 있습니다 %>%. 이것은 이것을 코딩하는 대신에 의미합니다 :

> as.Date("2014-01-01")
> as.character((sqrt(12)^2)

다음과 같이 할 수도 있습니다.

> "2014-01-01" %>% as.Date 
> 12 %>% sqrt %>% .^2 %>% as.character

나에게 이것은 더 읽기 쉽고 이것은 데이터 프레임을 넘어서는 사용 사례로 확장됩니다. 파이썬 언어가 비슷한 것을 지원합니까?


1
좋은 질문입니다. 특히 함수에 더 많은 인수가있는 경우에 관심이 있습니다. 마찬가지로 crime_by_state %>% filter(State=="New York", Year==2005) ...끝에서 dplyr 내 가장 일반적인 R 관용구를 교체하는 방법 .
Piotr Migdal

1
물론 많은 람다, 매핑 및 축소로 할 수 있지만 간단하고 가독성이 주요 포인트입니다.
Piotr Migdal

12
문제의 패키지는 magrittr입니다.
piccolbo

1
예, 같은 이유로 지금까지 작성된 모든 R 패키지는 Hadley가 작성했습니다. 그는 더 잘 알려져 있습니다. (여기 저조한 위장 부러워 경고)
piccolbo

1
이 문제를 해결 하는 stackoverflow.com/questions/33658355/…에 대한 답변을 참조하십시오 .
Piotr Migdal

답변:


34

이를 수행하는 한 가지 가능한 방법은라는 모듈을 사용하는 것 macropy입니다. Macropy를 사용하면 작성한 코드에 변환을 적용 할 수 있습니다. 따라서 a | b로 변환 될 수있다 b(a). 여기에는 많은 장점과 단점이 있습니다.

Sylvain Leroux가 언급 한 솔루션과 비교할 때 가장 큰 장점은 사용하려는 함수에 대해 중위 객체를 만들 필요가 없다는 것입니다. 변환을 사용하려는 코드 영역을 표시하기 만하면됩니다. 둘째, 변환이 런타임이 아닌 컴파일 타임에 적용되기 때문에 변환 된 코드는 런타임 중에 오버 헤드가 발생하지 않습니다. 모든 작업은 소스 코드에서 바이트 코드가 처음 생성 될 때 수행됩니다.

가장 큰 단점은 macropy가 작동하려면 특정 방법을 활성화해야한다는 것입니다 (나중에 언급). 더 빠른 런타임과 달리 소스 코드의 구문 분석은 계산적으로 더 복잡하므로 프로그램을 시작하는 데 더 오래 걸립니다. 마지막으로, 매크로 피에 익숙하지 않은 프로그래머가 코드를 이해하기 더 어려울 수 있음을 의미하는 구문 스타일을 추가합니다.

예제 코드 :

run.py

import macropy.activate 
# Activates macropy, modules using macropy cannot be imported before this statement
# in the program.
import target
# import the module using macropy

target.py

from fpipe import macros, fpipe
from macropy.quick_lambda import macros, f
# The `from module import macros, ...` must be used for macropy to know which 
# macros it should apply to your code.
# Here two macros have been imported `fpipe`, which does what you want
# and `f` which provides a quicker way to write lambdas.

from math import sqrt

# Using the fpipe macro in a single expression.
# The code between the square braces is interpreted as - str(sqrt(12))
print fpipe[12 | sqrt | str] # prints 3.46410161514

# using a decorator
# All code within the function is examined for `x | y` constructs.
x = 1 # global variable
@fpipe
def sum_range_then_square():
    "expected value (1 + 2 + 3)**2 -> 36"
    y = 4 # local variable
    return range(x, y) | sum | f[_**2]
    # `f[_**2]` is macropy syntax for -- `lambda x: x**2`, which would also work here

print sum_range_then_square() # prints 36

# using a with block.
# same as a decorator, but for limited blocks.
with fpipe:
    print range(4) | sum # prints 6
    print 'a b c' | f[_.split()] # prints ['a', 'b', 'c']

그리고 마지막으로 열심히 일하는 모듈입니다. 한 프로세스에서 다른 프로세스로 출력을 전달하기위한 에뮬레이션 쉘 구문으로 기능 파이프에 대해 fpipe라고 불렀습니다.

fpipe.py

from macropy.core.macros import *
from macropy.core.quotes import macros, q, ast

macros = Macros()

@macros.decorator
@macros.block
@macros.expr
def fpipe(tree, **kw):

    @Walker
    def pipe_search(tree, stop, **kw):
        """Search code for bitwise or operators and transform `a | b` to `b(a)`."""
        if isinstance(tree, BinOp) and isinstance(tree.op, BitOr):
            operand = tree.left
            function = tree.right
            newtree = q[ast[function](ast[operand])]
            return newtree

    return pipe_search.recurse(tree)

2
훌륭해 보이지만 Python 2.7에서만 작동합니다 (Python 3.4에서는 작동하지 않음).
Piotr Migdal

3
@fpipe 데코레이터와 동일한 작업을 수행하지만 또는 (|) 대신 오른쪽 시프트 (>>)를 재정의하는 종속성이없는 더 작은 라이브러리를 만들었습니다. pypi.org/project/pipeop
Robin Hilliard

여러 데코레이터를 사용하는 써드 파티 라이브러리를 요구하기 때문에 반대표를 던지는 것은 상당히 간단한 문제에 대한 매우 복잡한 솔루션입니다. 또한 파이썬 2 전용 솔루션입니다. 바닐라 파이썬 솔루션도 더 빠를 것이라고 확신합니다.
jramm

37

파이프는 Pandas 0.16.2 의 새로운 기능입니다. .

예:

import pandas as pd
from sklearn.datasets import load_iris

x = load_iris()
x = pd.DataFrame(x.data, columns=x.feature_names)

def remove_units(df):
    df.columns = pd.Index(map(lambda x: x.replace(" (cm)", ""), df.columns))
    return df

def length_times_width(df):
    df['sepal length*width'] = df['sepal length'] * df['sepal width']
    df['petal length*width'] = df['petal length'] * df['petal width']

x.pipe(remove_units).pipe(length_times_width)
x

참고 : Pandas 버전은 Python의 참조 의미를 유지합니다. 그렇기 때문에 length_times_width반환 값이 필요하지 않습니다. x제자리에서 수정 됩니다.


4
불행히도 이것은 데이터 프레임에서만 작동하므로 올바른 답으로 지정할 수 없습니다. 그러나 내가 염두에 둔 주요 사용 사례로 여기서 언급하는 것이 좋습니다.이를 데이터 프레임에 적용하는 것이 었습니다.
cantdutchthis

22

PyToolz [doc] 는 파이프 연산자 구문으로 정의되지 않은 파이프를 임의로 구성 할 수 있습니다.

빠른 시작은 위의 링크를 따르십시오. 다음은 비디오 자습서입니다. http://pyvideo.org/video/2858/functional-programming-in-python-with-pytoolz

In [1]: from toolz import pipe

In [2]: from math import sqrt

In [3]: pipe(12, sqrt, str)
Out[3]: '3.4641016151377544'

1
PyToolz는 훌륭한 포인터입니다. 하나의 링크가 죽고 다른 하나가 곧 죽어
간다고 말했듯이

2
그의 기본 URL은 http://matthewrocklin.com/blog 및 PyToolz toolz.readthedocs.io/en/latest 입니다. 아, internetz의 일시적인 현상 ...
smci dec.

18

파이썬 언어가 비슷한 것을 지원합니까?

"더 기능적인 파이핑 구문" 이 실제로 더 "기능적인"구문입니까? 대신 R에 "infix"구문을 추가합니다.

즉, Python의 문법 은 표준 연산자 이상의 중위 표기법을 직접 지원하지 않습니다.


정말로 그런 것이 필요하다면 Tomer Filiba의 코드를 시작점 으로 가져 와서 자신 만의 중위 표기법을 구현해야합니다.

Tomer Filiba의 코드 샘플 및 주석 ( http://tomerfiliba.com/blog/Infix-Operators/ ) :

from functools import partial

class Infix(object):
    def __init__(self, func):
        self.func = func
    def __or__(self, other):
        return self.func(other)
    def __ror__(self, other):
        return Infix(partial(self.func, other))
    def __call__(self, v1, v2):
        return self.func(v1, v2)

이 독특한 클래스의 인스턴스를 사용하여 이제 함수를 중위 연산자로 호출하는 데 새로운 "구문"을 사용할 수 있습니다.

>>> @Infix
... def add(x, y):
...     return x + y
...
>>> 5 |add| 6

18

개인 스크립팅을 위해 이것을 원한다면 Python 대신 Coconut을 사용하는 것이 좋습니다.

Coconut은 Python의 상위 집합입니다. 따라서 Coconut의 파이프 연산자를 사용할 수 |>있으며 나머지 Coconut 언어는 완전히 무시할 수 있습니다.

예를 들면 :

def addone(x):
    x + 1

3 |> addone

컴파일

# lots of auto-generated header junk

# Compiled Coconut: -----------------------------------------------------------

def addone(x):
    return x + 1

(addone)(3)

print(1 |> isinstance(int))... 형식 오류 : isinstance 1있어,이 개 인수를 예상
nyanpasu64을

1
@ jimbo1qaz이 문제가 계속 발생하면을 시도 print(1 |> isinstance$(int))하거나 가급적이면 1 |> isinstance$(int) |> print.
Solomon Ucko

@ Solomon Ucko 귀하의 대답이 잘못되었습니다. $가 Python 부분에 매핑되기 때문에 1 |> print$(2)호출합니다 print(2, 1). 하지만 print(1, 2)UFCS 및 magrittr과 일치하는 것을 원합니다 . 동기 부여 : 1 |> add(2) |> divide(6)0.5 여야하며 괄호가 필요하지 않습니다.
nyanpasu64

@ jimbo1qaz 예, 이전 의견이 잘못된 것 같습니다. 실제로 필요합니다 1 |> isinstance$(?, int) |> print. 다른 예 : 1 |> print$(?, 2), 1 |> (+)$(?, 2) |> (/)$(?, 6). 부분 적용을 위해 괄호를 피할 수 없다고 생각합니다.
Solomon Ucko 19.04.08

방법 모두 추한 보면 |>(+)$(?, 2), 내가 프로그래밍 언어와 수학의 설립이 나 구문의 유형을 사용하지 않는다는 결론에 도달, 괄호의 집합에 의존보다 더 추악한을하게했습니다. 더 나은 구문이 있으면 사용합니다 (예 : Dlang에는 UFCS가 있지만 산술 함수에 대해서는 IDK가 있거나 Python에 ..파이프 연산자 가있는 경우 ).
nyanpasu64

11

dfply모듈은. 자세한 정보는

https://github.com/kieferk/dfply

몇 가지 예는 다음과 같습니다.

from dfply import *
diamonds >> group_by('cut') >> row_slice(5)
diamonds >> distinct(X.color)
diamonds >> filter_by(X.cut == 'Ideal', X.color == 'E', X.table < 55, X.price < 500)
diamonds >> mutate(x_plus_y=X.x + X.y, y_div_z=(X.y / X.z)) >> select(columns_from('x')) >> head(3)

제 생각에는 이것은 정답으로 표시되어야합니다. 또한 dfply과 둘 다 dplython동일한 패키지 인 것으로 보입니다 . 그들 사이에 차이점이 있습니까? @BigDataScientist
InfiniteFlash

dfply, dplython, plydata패키지의 파이썬 포트입니다 dplyr그들은 구문 매우 유사 할 예정 있도록 패키지로 제공된다.
BigDataScientist

9

|>Elixir 의 파이프 연산자를 놓 쳤기 때문에 >>ast 라이브러리와 컴파일 / 실행을 사용하여 컴파일 타임에 Python 오른쪽 시프트 연산자를 매우 Elixir와 유사한 파이프로 재 해석하는 간단한 함수 데코레이터 (~ 50 줄의 코드)를 만들었습니다 .

from pipeop import pipes

def add3(a, b, c):
    return a + b + c

def times(a, b):
    return a * b

@pipes
def calc()
    print 1 >> add3(2, 3) >> times(4)  # prints 24

하는 일은 다음 a >> b(...)과 같이 다시 작성 하는 것입니다.b(a, ...) .

https://pypi.org/project/pipeop/

https://github.com/robinhilliard/pipes


9

sspipe 라이브러리 를 사용할 수 있습니다 . 두 개의 객체 ppx. 유사하게 x %>% f(y,z)쓸 수 x | p(f, y, z)있고 유사하게 x %>% .^2쓸 수 있습니다 x | px**2.

from sspipe import p, px
from math import sqrt

12 | p(sqrt) | px ** 2 | p(str)

8

건축 pipeInfix

Sylvain Leroux가 암시했듯이 Infix연산자를 사용하여 중위를 구성 할 수 있습니다 pipe. 이것이 어떻게 성취되는지 봅시다.

먼저 Tomer Filiba 의 코드입니다.

Tomer Filiba의 코드 샘플 및 주석 ( http://tomerfiliba.com/blog/Infix-Operators/ ) :

from functools import partial

class Infix(object):
    def __init__(self, func):
        self.func = func
    def __or__(self, other):
        return self.func(other)
    def __ror__(self, other):
        return Infix(partial(self.func, other))
    def __call__(self, v1, v2):
        return self.func(v1, v2)

이 독특한 클래스의 인스턴스를 사용하여 이제 함수를 중위 연산자로 호출하는 데 새로운 "구문"을 사용할 수 있습니다.

>>> @Infix
... def add(x, y):
...     return x + y
...
>>> 5 |add| 6

파이프 연산자는 파이프를 따라 물체의 인수로서 선행 물체를 통과하므로 x %>% f로 변환 될 수있다 f(x). 결과적으로 pipe연산자는 Infix다음과 같이 정의 할 수 있습니다 .

In [1]: @Infix
   ...: def pipe(x, f):
   ...:     return f(x)
   ...:
   ...:

In [2]: from math import sqrt

In [3]: 12 |pipe| sqrt |pipe| str
Out[3]: '3.4641016151377544'

부분적 적용에 대한 참고 사항

%>%연산자 dpylr는 함수의 첫 번째 인수를 통해 인수를 푸시하므로

df %>% 
filter(x >= 2) %>%
mutate(y = 2*x)

에 해당

df1 <- filter(df, x >= 2)
df2 <- mutate(df1, y = 2*x)

파이썬에서 비슷한 것을 얻는 가장 쉬운 방법은 currying 을 사용하는 것 입니다. toolz라이브러리는 제공 curry쉬운 카레 기능을 구성하게 장식 기능.

In [2]: from toolz import curry

In [3]: from datetime import datetime

In [4]: @curry
    def asDate(format, date_string):
        return datetime.strptime(date_string, format)
    ...:
    ...:

In [5]: "2014-01-01" |pipe| asDate("%Y-%m-%d")
Out[5]: datetime.datetime(2014, 1, 1, 0, 0)

공지 사항 |pipe|에 인수를 밀어 마지막 인수의 위치 , 즉

x |pipe| f(2)

에 해당

f(2, x)

커리 함수를 디자인 할 때 정적 인수 (예 : 많은 예제에 사용될 수있는 인수)는 매개 변수 목록의 앞부분에 배치해야합니다.

모듈의 toolz다양한 기능을 포함하여 많은 사전 커 리드 기능 이 포함되어 operator있습니다.

In [11]: from toolz.curried import map

In [12]: from toolz.curried.operator import add

In [13]: range(5) |pipe| map(add(2)) |pipe| list
Out[13]: [2, 3, 4, 5, 6]

대략 R에서 다음과 일치합니다.

> library(dplyr)
> add2 <- function(x) {x + 2}
> 0:4 %>% sapply(add2)
[1] 2 3 4 5 6

다른 중위 구분 기호 사용

다른 Python 연산자 메서드를 재정 의하여 Infix 호출을 둘러싼 기호를 변경할 수 있습니다. 예를 들어, 스위칭 __or____ror____mod____rmod__변화한다 |받는 오퍼레이터 mod오퍼레이터.

In [5]: 12 %pipe% sqrt %pipe% str
Out[5]: '3.4641016151377544'

6

내 2c 추가. 저는 개인적 으로 함수형 프로그래밍을 위해 패키지 fn 을 사용합니다 . 귀하의 예는

from fn import F, _
from math import sqrt

(F(sqrt) >> _**2 >> str)(12)

F부분 적용 및 구성을위한 기능적 스타일의 구문 설탕이있는 래퍼 클래스입니다. _익명 함수를위한 Scala 스타일 생성자입니다 (Python의 lambda) 와 유사합니다 . 변수를 나타내므로 _하나의 표현식에서 여러 객체를 결합 하여 더 많은 인수가있는 함수를 얻을 수 있습니다 (예 _ + _:) lambda a, b: a + b. 원하는만큼 사용할 수 F(sqrt) >> _**2 >> str있는 Callable개체가 생성됩니다.


내가 찾고있는 것-심지어 스칼라를 삽화로 언급하기도했습니다. 지금
사용해보기

@javadba 이것이 유용하다는 것을 알게되어 기쁩니다. 즉, 메모를 받아 _100 % 가동되지 않습니다 : 그것은 모든 파이썬 연산자를 지원하지 않습니다. 추가로, _대화 형 세션 에서 사용하려는 경우 다른 이름 (예 :)으로 가져와야합니다. from fn import _ as var대부분의 (전부는 아님) 대화 형 Python 셸 _이 마지막 할당되지 않은 반환 값을 나타내는 데 사용 하므로 가져온 객체를 섀도 잉하기 때문입니다.
Eli Korvigo

5

파이프 함수를 구현하기 위해 타사 라이브러리 또는 혼란스러운 연산자 속임수가 필요하지 않습니다. 기본 사항을 쉽게 익힐 수 있습니다.

파이프 함수가 실제로 무엇인지 정의하는 것으로 시작하겠습니다. 기본적으로는 표준 '내부'순서가 아니라 일련의 함수 호출을 논리적 순서로 표현하는 방법 일뿐입니다.

예를 들어 다음 함수를 살펴 보겠습니다.

def one(value):
  return value

def two(value):
  return 2*value

def three(value):
  return 3*value

별로 흥미롭지는 않지만 흥미로운 일이 일어나고 있다고 가정합니다. value . 순서대로 호출하여 각각의 출력을 다음으로 전달합니다. 바닐라 파이썬에서는 다음과 같습니다.

result = three(two(one(1)))

믿을 수 없을 정도로 가독성이 높지 않으며 더 복잡한 파이프 라인에서는 더 나빠질 것입니다. 따라서 다음은 초기 인수를받는 간단한 파이프 함수와이를 적용 할 일련의 함수입니다.

def pipe(first, *args):
  for fn in args:
    first = fn(first)
  return first

그것을 부르 자 :

result = pipe(1, one, two, three)

그것은 나에게 매우 읽기 쉬운 '파이프'구문처럼 보입니다 :). 연산자를 오버로딩하는 것보다 가독성이 떨어지는 지 모르겠습니다. 사실, 나는 그것이 더 읽기 쉬운 파이썬 이라고 주장하고 싶습니다 코드

다음은 OP의 예를 해결하는 겸손한 파이프입니다.

from math import sqrt
from datetime import datetime

def as_date(s):
  return datetime.strptime(s, '%Y-%m-%d')

def as_character(value):
  # Do whatever as.character does
  return value

pipe("2014-01-01", as_date)
pipe(12, sqrt, lambda x: x**2, as_character)

3

한 가지 대안은 워크 플로 도구 dask를 사용하는 것입니다. 구문 적으로 재미는 없지만 ...

var
| do this
| then do that

... 변수가 체인 아래로 흐르도록 허용하고 dask를 사용하면 가능한 경우 병렬화의 추가 이점을 얻을 수 있습니다.

dask를 사용하여 파이프 체인 패턴을 수행하는 방법은 다음과 같습니다.

import dask

def a(foo):
    return foo + 1
def b(foo):
    return foo / 2
def c(foo,bar):
    return foo + bar

# pattern = 'name_of_behavior': (method_to_call, variables_to_pass_in, variables_can_be_task_names)
workflow = {'a_task':(a,1),
            'b_task':(b,'a_task',),
            'c_task':(c,99,'b_task'),}

#dask.visualize(workflow) #visualization available. 

dask.get(workflow,'c_task')

# returns 100

엘릭서로 작업 한 후 저는 파이썬에서 파이핑 패턴을 사용하고 싶었습니다. 이것은 정확히 같은 패턴은 아니지만 비슷하고 내가 말했듯이 병렬화의 추가적인 이점이 있습니다. 워크 플로에서 다른 작업에 의존하지 않는 작업을 먼저 실행하도록 dask에 지시하면 병렬로 실행됩니다.

더 쉬운 구문을 원한다면 작업 이름 지정을 처리 할 수있는 것으로 래핑 할 수 있습니다. 물론이 상황에서 파이프를 첫 번째 인수로 사용하려면 모든 함수가 필요하며 패럴 라이 제이션의 이점을 잃게됩니다. 하지만 괜찮다면 다음과 같이 할 수 있습니다.

def dask_pipe(initial_var, functions_args):
    '''
    call the dask_pipe with an init_var, and a list of functions
    workflow, last_task = dask_pipe(initial_var, {function_1:[], function_2:[arg1, arg2]})
    workflow, last_task = dask_pipe(initial_var, [function_1, function_2])
    dask.get(workflow, last_task)
    '''
    workflow = {}
    if isinstance(functions_args, list):
        for ix, function in enumerate(functions_args):
            if ix == 0:
                workflow['task_' + str(ix)] = (function, initial_var)
            else:
                workflow['task_' + str(ix)] = (function, 'task_' + str(ix - 1))
        return workflow, 'task_' + str(ix)
    elif isinstance(functions_args, dict):
        for ix, (function, args) in enumerate(functions_args.items()):
            if ix == 0:
                workflow['task_' + str(ix)] = (function, initial_var)
            else:
                workflow['task_' + str(ix)] = (function, 'task_' + str(ix - 1), *args )
        return workflow, 'task_' + str(ix)

# piped functions
def foo(df):
    return df[['a','b']]
def bar(df, s1, s2):
    return df.columns.tolist() + [s1, s2]
def baz(df):
    return df.columns.tolist()

# setup 
import dask
import pandas as pd
df = pd.DataFrame({'a':[1,2,3],'b':[1,2,3],'c':[1,2,3]})

이제이 래퍼를 사용하여 다음 구문 패턴 중 하나를 따르는 파이프를 만들 수 있습니다.

# wf, lt = dask_pipe(initial_var, [function_1, function_2])
# wf, lt = dask_pipe(initial_var, {function_1:[], function_2:[arg1, arg2]})

이렇게 :

# test 1 - lists for functions only:
workflow, last_task =  dask_pipe(df, [foo, baz])
print(dask.get(workflow, last_task)) # returns ['a','b']

# test 2 - dictionary for args:
workflow, last_task = dask_pipe(df, {foo:[], bar:['string1', 'string2']})
print(dask.get(workflow, last_task)) # returns ['a','b','string1','string2']

이 하나의 문제는 :( 인수로 함수를 통과 할 수 있다는 것입니다
합법 스택

2

여기에 아주 멋진 pipe모듈이 있습니다. https://pypi.org/project/pipe/ 오버로드 | 연산자와 같은 많은 파이프 기능을 제공합니다 add, first, where, tail.

>>> [1, 2, 3, 4] | where(lambda x: x % 2 == 0) | add
6

>>> sum([1, [2, 3], 4] | traverse)
10

또한 파이프 함수를 작성하는 것은 매우 쉽습니다.

@Pipe
def p_sqrt(x):
    return sqrt(x)

@Pipe
def p_pr(x):
    print(x)

9 | p_sqrt | p_pr

0

파이프 기능은 점으로 pandas 메서드를 구성하여 얻을 수 있습니다. 다음은 아래의 예입니다.

샘플 데이터 프레임을로드합니다.

import seaborn    
iris = seaborn.load_dataset("iris")
type(iris)
# <class 'pandas.core.frame.DataFrame'>

점을 사용하여 pandas 메서드의 구성을 설명합니다.

(iris.query("species == 'setosa'")
     .sort_values("petal_width")
     .head())

필요한 경우 panda 데이터 프레임에 새 메서드를 추가 할 수 있습니다 ( 예 : 여기 에서 수행됨 ).

pandas.DataFrame.new_method  = new_method
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.