인덱스가 MultiIndex 인 데이터 프레임의 행을 선택 / 필터링하는 가장 일반적인 Pandas 방법은 무엇입니까?
- 단일 값 / 라벨을 기준으로 슬라이스
- 하나 이상의 수준에서 여러 레이블을 기반으로 슬라이스
- 부울 조건 및 표현식 필터링
- 어떤 상황에서 적용 가능한 방법
단순성을위한 가정 :
- 입력 데이터 프레임에 중복 인덱스 키가 없습니다.
- 아래의 입력 데이터 프레임에는 두 가지 수준 만 있습니다. (여기에 표시된 대부분의 솔루션은 N 레벨로 일반화됩니다.)
입력 예 :
mux = pd.MultiIndex.from_arrays([ list('aaaabbbbbccddddd'), list('tuvwtuvwtuvwtuvw') ], names=['one', 'two']) df = pd.DataFrame({'col': np.arange(len(mux))}, mux) col one two a t 0 u 1 v 2 w 3 b t 4 u 5 v 6 w 7 t 8 c u 9 v 10 d w 11 t 12 u 13 v 14 w 15
질문 1 : 단일 항목 선택
“1”수준에서 “a”가있는 행을 어떻게 선택합니까?
col
one two
a t 0
u 1
v 2
w 3
또한 출력에서 레벨 “1”을 어떻게 드롭 할 수 있습니까?
col
two
t 0
u 1
v 2
w 3
질문 1b
레벨 “2”에서 값 “t”를 가진 모든 행을 어떻게 슬라이스합니까?
col
one two
a t 0
b t 4
t 8
d t 12
질문 2 : 레벨에서 여러 값 선택
레벨 “1”에서 항목 “b”및 “d”에 해당하는 행을 어떻게 선택할 수 있습니까?
col
one two
b t 4
u 5
v 6
w 7
t 8
d w 11
t 12
u 13
v 14
w 15
질문 2b
“2”수준에서 “t”및 “w”에 해당하는 모든 값을 얻으려면 어떻게해야합니까?
col
one two
a t 0
w 3
b t 4
w 7
t 8
d w 11
t 12
w 15
질문 3 : 단일 횡단면 슬라이스 (x, y)
인덱스에 대한 특정 값이있는 단일 행과 같은 횡단면을 어떻게 검색 df
합니까? 특히, 어떻게의 단면을 검색 할 ('c', 'u')
의해 주어진,
col
one two
c u 9
질문 4 : 여러 횡단면 분할 [(a, b), (c, d), ...]
('c', 'u')
및에 해당하는 두 행을 어떻게 선택 ('a', 'w')
합니까?
col
one two
c u 9
a w 3
질문 5 : 레벨 당 슬라이스 된 항목 1 개
“1”수준의 “a”또는 “2”수준의 “t”에 해당하는 모든 행을 검색하려면 어떻게해야합니까?
col
one two
a t 0
u 1
v 2
w 3
b t 4
t 8
d t 12
질문 6 : 임의 슬라이스
특정 단면을 슬라이스하려면 어떻게해야합니까? “a”와 “b”의 경우 하위 수준이 “u”및 “v”인 모든 행을 선택하고 “d”의 경우 하위 수준이 “w”인 행을 선택하고 싶습니다.
col
one two
a u 1
v 2
b u 5
v 6
d w 11
w 15
질문 7은 숫자 수준으로 구성된 고유 한 설정을 사용합니다.
np.random.seed(0) mux2 = pd.MultiIndex.from_arrays([ list('aaaabbbbbccddddd'), np.random.choice(10, size=16) ], names=['one', 'two']) df2 = pd.DataFrame({'col': np.arange(len(mux2))}, mux2) col one two a 5 0 0 1 3 2 3 3 b 7 4 9 5 3 6 5 7 2 8 c 4 9 7 10 d 6 11 8 12 8 13 1 14 6 15
질문 7 : 다중 인덱스의 개별 수준에서 숫자 부등식으로 필터링
“2”수준의 값이 5보다 큰 모든 행을 가져 오려면 어떻게합니까?
col
one two
b 7 4
9 5
c 7 10
d 6 11
8 12
8 13
6 15
참고 :이 게시물은 MultiIndex를 만드는 방법, 할당 작업을 수행하는 방법 또는 성능 관련 토론을 다루지 않습니다 (이 내용은 나중에 별도의 주제 임).
답변
멀티 인덱싱 / 고급 인덱싱
참고이
게시물은 다음과 같은 방식으로 구성됩니다.
- OP에 제시된 질문은 하나씩 처리됩니다.
- 각 질문에 대해이 문제를 해결하고 예상 된 결과를 얻는 데 적용 할 수있는 하나 이상의 방법이 시연됩니다.
추가 기능, 구현 세부 정보 및 당면한 주제에 대한 간단한 정보에 관심이있는 독자를 위해 참고 (이와 매우 유사 함)가 포함됩니다. 이 노트는 문서를 샅샅이 뒤지고 다양한 모호한 기능을 발견하고 내 자신의 (제한된) 경험을 통해 수집되었습니다.
모든 코드 샘플은 pandas v0.23.4, python3.7 에서 생성 및 테스트되었습니다 . 명확하지 않거나 사실적으로 잘못된 것이 있거나 사용 사례에 적용 할 수있는 해결책을 찾지 못한 경우 자유롭게 편집을 제안하거나 의견에 대한 설명을 요청하거나 새 질문을여십시오. 해당되는 경우 …. .
다음은 우리가 자주 다시 살펴볼 몇 가지 일반적인 관용구 (이하 Four Idioms라고 함)에 대한 소개입니다.
-
DataFrame.loc
-라벨 별 선택을위한 일반적인 솔루션 (pd.IndexSlice
슬라이스와 관련된 더 복잡한 애플리케이션의 경우 + ) -
DataFrame.xs
-Series / DataFrame에서 특정 단면을 추출합니다. -
DataFrame.query
-슬라이싱 및 / 또는 필터링 작업을 동적으로 지정합니다 (즉, 동적으로 평가되는 표현식으로. 다른 시나리오보다 일부 시나리오에 더 적용 가능합니다. 또한 다중 인덱스에 대한 쿼리에 대한 문서의이 섹션을 참조하십시오 . -
를 사용하여 생성 된 마스크를 사용한 부울 인덱싱
MultiIndex.get_level_values
(Index.isin
특히 여러 값으로 필터링 할 때 와 함께 자주 사용 ). 이것은 또한 일부 상황에서 매우 유용합니다.
주어진 상황에 적용 할 수있는 내용을 더 잘 이해하기 위해 4 가지 관용구 측면에서 다양한 슬라이싱 및 필터링 문제를 살펴 보는 것이 좋습니다. 모든 관용구가 모든 상황에서 똑같이 잘 작동하지는 않는다는 것을 이해하는 것이 매우 중요합니다. 관용구가 아래 문제에 대한 잠재적 해결책으로 나열되지 않은 경우 해당 관용구를 해당 문제에 효과적으로 적용 할 수 없습니다.
질문 1
“1”수준에서 “a”가있는 행을 어떻게 선택합니까?
col one two a t 0 u 1 v 2 w 3
loc
대부분의 상황에 적용 할 수있는 범용 솔루션으로을 사용할 수 있습니다 .
df.loc[['a']]
이 시점에서
TypeError: Expected tuple, got str
이는 이전 버전의 pandas를 사용하고 있음을 의미합니다. 업그레이드를 고려하십시오! 그렇지 않으면 df.loc[('a', slice(None)), :]
.
또는 xs
단일 단면을 추출하므로 여기에서 사용할 수 있습니다 . levels
및 axis
인수에 유의하십시오 (여기에서 합리적인 기본값을 가정 할 수 있음).
df.xs('a', level=0, axis=0, drop_level=False)
# df.xs('a', drop_level=False)
여기서는 결과 (우리가 슬라이스 한 수준)에서 “1”수준이 떨어지는 drop_level=False
것을 방지하기 위해 인수가 필요합니다 xs
.
여기에 또 다른 옵션은 다음을 사용하는 것입니다 query
.
df.query("one == 'a'")
인덱스에 이름이없는 경우 쿼리 문자열을 "ilevel_0 == 'a'"
.
마지막으로 다음을 사용합니다 get_level_values
.
df[df.index.get_level_values('one') == 'a']
# If your levels are unnamed, or if you need to select by position (not label),
# df[df.index.get_level_values(0) == 'a']
또한 출력에서 레벨 “1”을 어떻게 드롭 할 수 있습니까?
col two t 0 u 1 v 2 w 3
이것은 다음 중 하나를 사용하여 쉽게 수행 할 수 있습니다.
df.loc['a'] # Notice the single string argument instead the list.
또는,
df.xs('a', level=0, axis=0, drop_level=True)
# df.xs('a')
drop_level
인수를 생략 할 수 있습니다 ( True
기본적으로 가정 됨 ).
참고
필터링 된 DataFrame은 DataFrame을 인쇄 할 때 표시되지 않더라도 여전히 모든 수준을 가질 수 있습니다. 예를 들면v = df.loc[['a']] print(v) col one two a t 0 u 1 v 2 w 3 print(v.index) MultiIndex(levels=[['a', 'b', 'c', 'd'], ['t', 'u', 'v', 'w']], labels=[[0, 0, 0, 0], [0, 1, 2, 3]], names=['one', 'two'])
다음을 사용하여 이러한 수준을 제거 할 수 있습니다
MultiIndex.remove_unused_levels
.v.index = v.index.remove_unused_levels() print(v.index) MultiIndex(levels=[['a'], ['t', 'u', 'v', 'w']], labels=[[0, 0, 0, 0], [0, 1, 2, 3]], names=['one', 'two'])
질문 1b
레벨 “2”에서 값 “t”를 가진 모든 행을 어떻게 슬라이스합니까?
col one two a t 0 b t 4 t 8 d t 12
직관적으로 다음과 slice()
같은 것을 원할 것입니다 .
df.loc[(slice(None), 't'), :]
It Just Works! ™하지만 투박합니다. pd.IndexSlice
여기서 API를 사용하여보다 자연스러운 슬라이싱 구문을 촉진 할 수 있습니다.
idx = pd.IndexSlice
df.loc[idx[:, 't'], :]
이것은 훨씬 더 깨끗합니다.
참고 열에
후행 슬라이스가:
필요한 이유는 무엇 입니까? 는loc
두 축 (axis=0
또는
axis=1
)을 따라 선택하고 슬라이스하는 데 사용할 수 있기 때문 입니다. 슬라이싱을 수행 할 축을 명시 적으로 지정하지 않으면 작업이 모호해집니다. 슬라이싱 에 대한 문서 에서 큰 빨간색 상자를 참조하십시오 .모호한 음영을 제거
loc
하려면axis
매개 변수를 허용합니다 .df.loc(axis=0)[pd.IndexSlice[:, 't']]
axis
매개 변수가 없으면 (즉,을 수행하여df.loc[pd.IndexSlice[:, 't']]
) slicing이 열에있는 것으로 가정KeyError
하고이 상황에서 a 가 발생합니다.이것은 슬라이서에 설명되어 있습니다. 그러나이 게시물의 목적을 위해 모든 축을 명시 적으로 지정합니다.
으로 xs
, 그것은이다
df.xs('t', axis=0, level=1, drop_level=False)
으로 query
, 그것은이다
df.query("two == 't'")
# Or, if the first level has no name,
# df.query("ilevel_1 == 't'")
마지막으로을 사용 get_level_values
하면
df[df.index.get_level_values('two') == 't']
# Or, to perform selection by position/integer,
# df[df.index.get_level_values(1) == 't']
모두 같은 효과입니다.
질문 2
레벨 “1”에서 항목 “b”및 “d”에 해당하는 행을 어떻게 선택할 수 있습니까?
col one two b t 4 u 5 v 6 w 7 t 8 d w 11 t 12 u 13 v 14 w 15
loc을 사용하면 목록을 지정하여 유사한 방식으로 수행됩니다.
df.loc[['b', 'd']]
위의 “b”및 “d”선택 문제를 해결하려면 다음을 사용할 수도 있습니다 query
.
items = ['b', 'd']
df.query("one in @items")
# df.query("one == @items", parser='pandas')
# df.query("one in ['b', 'd']")
# df.query("one == ['b', 'd']", parser='pandas')
참고
예, 기본 파서는'pandas'
이지만이 구문이 일반적으로 파이썬이 아니라는 점을 강조하는 것이 중요합니다. Pandas 파서는 표현식과 약간 다른 구문 분석 트리를 생성합니다. 이는 일부 작업을보다 직관적으로 지정하기 위해 수행됩니다. 자세한 내용은 pd.eval ()을 사용하여 pandas의 Dynamic Expression Evaluation에 대한 내 게시물을 참조하십시오
.
그리고 get_level_values
+ Index.isin
:
df[df.index.get_level_values("one").isin(['b', 'd'])]
질문 2b
레벨 “2”에서 “t”및 “w”에 해당하는 모든 값을 어떻게 얻을 수 있습니까?
col one two a t 0 w 3 b t 4 w 7 t 8 d w 11 t 12 w 15
로 loc
이 가능 단지 와 연동 해에 pd.IndexSlice
.
df.loc[pd.IndexSlice[:, ['t', 'w']], :]
제 대장 :
의 pd.IndexSlice[:, ['t', 'w']]
수단은, 상기 제 레벨을 가로 질러 슬라이스. 쿼리되는 수준의 깊이가 증가함에 따라 더 많은 슬라이스를 지정해야합니다. 그러나 슬라이스되는 레벨 이상으로 더 많은 레벨을 지정할 필요는 없습니다 .
으로는 query
,이입니다
items = ['t', 'w']
df.query("two in @items")
# df.query("two == @items", parser='pandas')
# df.query("two in ['t', 'w']")
# df.query("two == ['t', 'w']", parser='pandas')
부착 get_level_values
하고 Index.isin
(상기와 유사 함) :
df[df.index.get_level_values('two').isin(['t', 'w'])]
질문 3
인덱스에 대한 특정 값이있는 단일 행과 같은 횡단면을 어떻게 검색
df
합니까? 특히, 어떻게의 단면을 검색 할('c', 'u')
의해 주어진,col one two c u 9
loc
키 튜플을 지정하여 사용 합니다.
df.loc[('c', 'u'), :]
또는,
df.loc[pd.IndexSlice[('c', 'u')]]
참고이
시점에서PerformanceWarning
다음과 같은 문제가 발생할 수 있습니다 .PerformanceWarning: indexing past lexsort depth may impact performance.
이는 색인이 정렬되지 않았 음을 의미합니다. pandas는 최적의 검색 및 검색을 위해 정렬되는 인덱스 (이 경우 문자열 값을 다루기 때문에 사전 순)에 의존합니다. 빠른 수정은
DataFrame.sort_index
. 이러한 쿼리를 여러 번 동시에 수행하려는 경우 성능 관점에서 특히 바람직합니다.df_sort = df.sort_index() df_sort.loc[('c', 'u')]
MultiIndex.is_lexsorted()
색인이 정렬되었는지 여부를 확인 하는 데 사용할 수도 있습니다 . 이 함수는True
또는False
그에 따라 반환합니다 . 이 함수를 호출하여 추가 정렬 단계가 필요한지 여부를 결정할 수 있습니다.
를 사용하면 xs
다른 모든 인수를 적절한 기본값으로 설정하여 단일 튜플을 첫 번째 인수로 다시 전달합니다.
df.xs(('c', 'u'))
를 사용 query
하면 상황이 약간 어색해집니다.
df.query("one == 'c' and two == 'u'")
이제 이것은 일반화하기가 상대적으로 어려울 것임을 알 수 있습니다. 그러나이 특정 문제는 여전히 괜찮습니다.
여러 수준에 걸친 액세스로 get_level_values
계속 사용할 수 있지만 권장되지는 않습니다.
m1 = (df.index.get_level_values('one') == 'c')
m2 = (df.index.get_level_values('two') == 'u')
df[m1 & m2]
질문 4
('c', 'u')
및에 해당하는 두 행을 어떻게 선택('a', 'w')
합니까?col one two c u 9 a w 3
을 사용하면 다음과 loc
같이 간단합니다.
df.loc[[('c', 'u'), ('a', 'w')]]
# df.loc[pd.IndexSlice[[('c', 'u'), ('a', 'w')]]]
를 사용하면 query
횡단면과 레벨을 반복하여 쿼리 문자열을 동적으로 생성해야합니다.
cses = [('c', 'u'), ('a', 'w')]
levels = ['one', 'two']
# This is a useful check to make in advance.
assert all(len(levels) == len(cs) for cs in cses)
query = '(' + ') or ('.join([
' and '.join([f"({l} == {repr(c)})" for l, c in zip(levels, cs)])
for cs in cses
]) + ')'
print(query)
# ((one == 'c') and (two == 'u')) or ((one == 'a') and (two == 'w'))
df.query(query)
100 % 권장하지 않습니다! 그러나 가능합니다.
질문 5
“1”수준의 “a”또는 “2”수준의 “t”에 해당하는 모든 행을 검색하려면 어떻게해야합니까?
col one two a t 0 u 1 v 2 w 3 b t 4 t 8 d t 12
이 사실에 할 매우 어려운 loc
정확성을 보장 하고 여전히 코드 선명도를 유지. df.loc[pd.IndexSlice['a', 't']]
잘못된 경우 df.loc[pd.IndexSlice[('a', 't')]]
(예 : 단면 선택) 로 해석됩니다 . pd.concat
각 레이블을 개별적으로 처리 하는 솔루션을 생각할 수 있습니다 .
pd.concat([
df.loc[['a'],:], df.loc[pd.IndexSlice[:, 't'],:]
])
col
one two
a t 0
u 1
v 2
w 3
t 0 # Does this look right to you? No, it isn't!
b t 4
t 8
d t 12
그러나 행 중 하나가 중복되었음을 알 수 있습니다. 이는 해당 행이 두 슬라이싱 조건을 모두 충족하여 두 번 표시 되었기 때문입니다. 대신해야합니다.
v = pd.concat([
df.loc[['a'],:], df.loc[pd.IndexSlice[:, 't'],:]
])
v[~v.index.duplicated()]
그러나 DataFrame에 기본적으로 원하는 중복 인덱스가 포함되어 있으면 유지되지 않습니다. 극도로주의해서 사용하십시오 .
을 사용 query
하면 이것은 어리석게도 간단합니다.
df.query("one == 'a' or two == 't'")
을 사용하면 get_level_values
여전히 간단하지만 우아하지는 않습니다.
m1 = (df.index.get_level_values('one') == 'a')
m2 = (df.index.get_level_values('two') == 't')
df[m1 | m2]
질문 6
특정 단면을 슬라이스하려면 어떻게해야합니까? “a”와 “b”의 경우 하위 수준이 “u”및 “v”인 모든 행을 선택하고 “d”의 경우 하위 수준이 “w”인 행을 선택하고 싶습니다.
col one two a u 1 v 2 b u 5 v 6 d w 11 w 15
이것은 4 개 숙어의 적용 가능성을 이해하는 데 도움이되도록 추가 한 특별한 경우입니다. 이것은 슬라이스가 매우 구체적이고 실제 패턴을 따르지 않기 때문에 어느 것도 효과적으로 작동 하지 않는 경우입니다.
일반적으로 이와 같은 슬라이싱 문제는 키 목록을에 명시 적으로 전달해야합니다 loc
. 이를 수행하는 한 가지 방법은 다음과 같습니다.
keys = [('a', 'u'), ('a', 'v'), ('b', 'u'), ('b', 'v'), ('d', 'w')]
df.loc[keys, :]
타이핑을 저장하려면 “a”, “b”및 하위 수준을 분할하는 패턴이 있음을 인식 할 수 있으므로 분할 작업을 두 부분으로 나눌 수 concat
있으며 결과는 다음 과 같습니다.
pd.concat([
df.loc[(('a', 'b'), ('u', 'v')), :],
df.loc[('d', 'w'), :]
], axis=0)
“a”및 “b”에 대한 슬라이싱 사양 (('a', 'b'), ('u', 'v'))
은 색인화되는 동일한 하위 수준이 각 수준에 대해 동일하기 때문에 약간 더 깔끔 합니다.
질문 7
“2”수준의 값이 5보다 큰 모든 행을 가져 오려면 어떻게합니까?
col one two b 7 4 9 5 c 7 10 d 6 11 8 12 8 13 6 15
이것은 query
,
df2.query("two > 5")
그리고 get_level_values
.
df2[df2.index.get_level_values('two') > 5]
참고이
예제와 유사하게 이러한 구문을 사용하여 임의의 조건을 기준으로 필터링 할 수 있습니다. 일반적으로, 그 기억 유용loc
하고xs
있는 동안, 레이블 기반 인덱싱을 위해 특별히입니다query
및
get_level_values
필터링을위한 일반적인 조건 마스크를 구축하기위한 도움이됩니다.
보너스 질문
MultiIndex
열 을 슬라이스해야하는 경우 어떻게 합니까?
실제로 여기에있는 대부분의 솔루션은 약간만 변경하여 열에도 적용 할 수 있습니다. 치다:
np.random.seed(0)
mux3 = pd.MultiIndex.from_product([
list('ABCD'), list('efgh')
], names=['one','two'])
df3 = pd.DataFrame(np.random.choice(10, (3, len(mux))), columns=mux3)
print(df3)
one A B C D
two e f g h e f g h e f g h e f g h
0 5 0 3 3 7 9 3 5 2 4 7 6 8 8 1 6
1 7 7 8 1 5 9 8 9 4 3 0 3 5 0 2 3
2 8 1 3 3 3 7 0 1 9 9 0 4 7 3 2 7
다음은 열과 함께 작동하도록하기 위해 Four Idioms를 변경해야합니다.
-
와 슬라이스
loc
, 사용df3.loc[:, ....] # Notice how we slice across the index with `:`.
또는,
df3.loc[:, pd.IndexSlice[...]]
-
xs
적절하게 사용하려면 인수를 전달하십시오axis=1
. -
를 사용하여 열 수준 값에 직접 액세스 할 수 있습니다
df.columns.get_level_values
. 그런 다음 다음과 같은 작업을 수행해야합니다.df.loc[:, {condition}]
어디에
{condition}
사용하여 구축 몇 가지 조건을 나타냅니다columns.get_level_values
. -
를 사용하려면
query
유일한 옵션은 전치, 인덱스 쿼리, 다시 전치하는 것입니다.df3.T.query(...).T
권장하지 않음, 다른 세 가지 옵션 중 하나를 사용하십시오.
답변
최근에 위의 솔루션 중 어느 것도 원하는 결과를 생성 할 수없는 3+ 수준의 다중 인덱스 데이터 프레임이있는 사용 사례를 발견했습니다. 물론 위의 솔루션이 내 사용 사례에서 작동 할 가능성이 높으며 여러 가지를 시도했지만 사용 가능한 시간에 작동하도록 만들 수 없었습니다.
나는 전문가와는 거리가 멀지 만 위의 포괄적 인 답변에 나열되지 않은 솔루션을 우연히 발견했습니다. 나는 솔루션이 어떤 식 으로든 최적이라는 보장을하지 않습니다.
이것은 위의 질문 # 6과 약간 다른 결과를 얻는 다른 방법입니다. (및 기타 질문도 가능)
특히 나는 찾고 있었다 :
- 한 수준의 인덱스에서 두 개 이상의 값을 선택하고 다른 수준의 인덱스에서 단일 값을 선택하는 방법
- 데이터 프레임 출력에서 이전 작업의 인덱스 값을 유지하는 방법입니다.
기어의 멍키 렌치로 (하지만 완전히 고정 가능) :
- 인덱스의 이름이 지정되지 않았습니다.
아래 장난감 데이터 프레임에서 :
index = pd.MultiIndex.from_product([['a','b'],
['stock1','stock2','stock3'],
['price','volume','velocity']])
df = pd.DataFrame([1,2,3,4,5,6,7,8,9,
10,11,12,13,14,15,16,17,18],
index)
0
a stock1 price 1
volume 2
velocity 3
stock2 price 4
volume 5
velocity 6
stock3 price 7
volume 8
velocity 9
b stock1 price 10
volume 11
velocity 12
stock2 price 13
volume 14
velocity 15
stock3 price 16
volume 17
velocity 18
물론 아래 작품을 사용하면 :
df.xs(('stock1', 'velocity'), level=(1,2))
0
a 3
b 12
하지만 다른 결과를 원했기 때문에 그 결과를 얻는 방법은 다음과 같습니다.
df.iloc[df.index.isin(['stock1'], level=1) &
df.index.isin(['velocity'], level=2)]
0
a stock1 velocity 3
b stock1 velocity 12
그리고 한 수준에서 두 개 이상의 값을, 다른 수준에서 단일 (또는 2+) 값을 원하는 경우 :
df.iloc[df.index.isin(['stock1','stock3'], level=1) &
df.index.isin(['velocity'], level=2)]
0
a stock1 velocity 3
stock3 velocity 9
b stock1 velocity 12
stock3 velocity 18
위의 방법은 아마도 약간 투박하지만 내 요구를 충족 시켰고 보너스로 이해하고 읽기가 더 쉬웠습니다.