불리언 데이터 (Boolean Data)

서론

이 장에서는 불리언(boolean) 데이터를 소개합니다. 불리언 데이터는 True(참) 또는 False(거짓) 값을 가질 수 있는 데이터입니다(1 또는 0으로 인코딩될 수도 있습니다). 먼저 파이썬의 기본적인 참/거짓 불리언 변수를 살펴본 후, 데이터 프레임에서 참/거짓이 어떻게 작동하는지 알아보겠습니다.

불리언 (Booleans)

여러분이 수행하게 될 가장 중요한 작업 중 일부는 TrueFalse 값(불리언 데이터 타입이라고도 함)을 사용하는 것입니다. 이것들은 1과 같은 숫자와 마찬가지로 파이썬의 핵심적인 변수 타입입니다.

불리언 변수와 조건

변수에 True 또는 False 값을 할당하는 것은 다른 변수 할당과 동일합니다:

코드 보기
bool_variable = True
bool_variable
True

불리언과 관련된 연산에는 두 가지 유형이 있습니다. 기존 불리언을 결합하는 불리언 연산(boolean operations)과, 실행 시 불리언을 생성하는 조건 연산(condition operations)입니다.

불리언을 반환하는 불리언 연산자는 다음과 같습니다:

연산자 설명
x and y xy가 모두 True인가?
x or y xy 중 적어도 하나가 True인가?
not x x가 False인가?

이것들은 예상한 대로 작동합니다. True and FalseFalse로 평가되고, True or FalseTrue로 평가됩니다. 또한 not 키워드도 있습니다. 예를 들어:

코드 보기
not True
False

예상한 대로 결과가 나옵니다.

조건은 불리언으로 평가되는 표현식입니다. 간단한 예로 10 == 20이 있습니다. ==는 양쪽의 객체를 비교하여 이 같으면 True를 반환하는 연산자입니다. 다만 서로 다른 데이터 타입을 비교할 때는 주의해야 합니다.

다음은 불리언을 반환하는 조건 연산자 표입니다:

연산자 설명
x == y xy의 값이 같은가?
x != y xy의 값이 같지 않은가?
x > y xy보다 큰가?
x >= y xy보다 크거나 같은가?
x < y xy보다 작은가?
x <= y xy보다 작거나 같은가?
x is y xy가 동일한 객체인가?

표에서 볼 수 있듯이 ==의 반대는 !=이며, ’값이 같지 않다’로 읽을 수 있습니다. 다음은 ==의 예입니다:

코드 보기
boolean_condition = 10 == 20
print(boolean_condition)
False

{.callout-note} 연습 문제 `not (not True)`는 무엇으로 평가될까요?

조건문의 진정한 힘은 더 복잡한 예제에서 사용될 때 나타납니다. 조건을 평가하는 주요 키워드로는 if, else, and, or, in, not, is 등이 있습니다. 다음은 이러한 조건 키워드가 어떻게 작동하는지 보여주는 예제입니다:

코드 보기
name = "Ada"
score = 99

if name == "Ada" and score > 90:
    print("Ada, you achieved a high score.")

if name == "Smith" or score > 90:
    print("You could be called Smith or have a high score")

if name != "Smith" and score > 90:
    print("You are not called Smith and you have a high score")
Ada, you achieved a high score.
You could be called Smith or have a high score
You are not called Smith and you have a high score

이 세 가지 조건은 모두 True로 평가되므로 세 메시지가 모두 출력됩니다. ==!=가 각각 같음과 같지 않음을 테스트한다면, isnot 키워드는 무엇을 위한 것인지 궁금할 수 있습니다. 파이썬의 모든 것은 객체(object)이며, 객체에 값이 할당될 수 있다는 점을 기억하십시오. ==!=을 비교하고, isnot객체 자체를 비교합니다. 예를 들어:

코드 보기
name_list = ["Ada", "Adam"]
name_list_two = ["Ada", "Adam"]

# 값 비교
print(name_list == name_list_two)

# 객체 비교
print(name_list is name_list_two)
True
False

분기되는 if 문이 너무 많은 코드는 본인이나 코드를 읽는 다른 사람에게 별로 도움이 되지 않습니다. 일부 자동 코드 검사기는 이를 감지하여 코드가 너무 복잡하다고 알려줄 것입니다. 거의 대부분의 경우, 수많은 중첩된 if 문을 사용하는 것보다 더 명확하고 나은 방식으로 코드를 재작성할 방법이 존재합니다.

가장 유용한 조건 키워드 중 하나는 in입니다. 이 키워드는 변수를 골라내거나 어떤 것이 있어야 할 곳에 있는지 확인하는 데 사용되므로 대부분의 개발자가 하루에도 수십 번씩 사용하게 됩니다.

코드 보기
name_list = ["Lovelace", "Smith", "Hopper", "Babbage"]

print("Lovelace" in name_list)

print("Bob" in name_list)
True
False

{.callout-note} 연습 문제 `in`을 사용하여 문자열 "Walloping weasels"에 "a"가 있는지 확인해 보세요. "Anodyne"에 "a"가 `in`으로 포함되어 있나요?

반대 키워드는 not in입니다.

마지막으로, 언젠가는 꼭 사용하게 될 조건 구조는 ifelse 구조입니다:

코드 보기
score = 98

if score == 100:
    print("Top marks!")
elif score > 90 and score < 100:
    print("High score!")
elif score > 10 and score <= 90:
    pass
else:
    print("Better luck next time.")
High score!

이 코드는 점수가 11점에서 90점 사이일 때는 아무 작업도 하지 않고, 그 외의 경우에만 메시지를 출력합니다.

{.callout-note} 연습 문제 점수가 90점 초과이면 "well done", 40점에서 90점 사이이면 "good", 그 외의 경우에는 "bad luck"을 출력하는 새로운 `if` ... `elif` ... `else` 문을 만들어 보세요.

파이썬의 멋진 기능 중 하나는 한 줄에 여러 불리언 비교를 할 수 있다는 점입니다.

코드 보기
a, b = 3, 6

1 < a < b < 20
True

리스트 컴프리헨션에서의 조건문

리스트 컴프리헨션(list comprehensions)은 파이썬에서 매우 유용한 패턴입니다. 다음은 0부터 시작하는 처음 12개의 숫자 리스트를 생성하는 간단한 예제입니다:

코드 보기
[x for x in range(12)]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

여기에 불리언 조건을 추가할 수 있습니다. if 문 뒤에 x 값에 따라 True 또는 False로 평가되는 조건을 추가합니다. 예를 들어, 2로 나누어지는 숫자만 요청할 수 있습니다:

코드 보기
[x for x in range(12) if x % 2 == 0]
[0, 2, 4, 6, 8, 10]

이 기법은 else 절과도 함께 사용할 수 있습니다(단, 이 경우에는 ifelse를 모두 for x in ... 부분 앞으로 옮겨야 합니다).

코드 보기
[x if x % 2 == 0 else "Not divisible by 2" for x in range(12)]
[0,
 'Not divisible by 2',
 2,
 'Not divisible by 2',
 4,
 'Not divisible by 2',
 6,
 'Not divisible by 2',
 8,
 'Not divisible by 2',
 10,
 'Not divisible by 2']

참 같은 값(Truthy)과 거짓 같은 값(Falsy)

파이썬 객체는 불리언 값을 반환하는 표현식에 사용될 수 있습니다. 예를 들어 if listy와 같이 리스트 listy를 직접 사용하는 경우입니다. 비어 있는 내장 파이썬 객체는 보통 False로 평가되며, 이를 ’거짓 같은 값(Falsy)’이라고 합니다. 반대로 비어 있지 않은 객체는 True로 평가되며 ’참 같은 값(truthy)’이라고 합니다. 몇 가지 예제를 살펴보겠습니다:

코드 보기
listy = []
other_listy = [1, 2, 3]

if not (listy):
    print("Falsy")
else:
    print("Truthy")
Falsy
코드 보기
if not (other_listy):
    print("Falsy")
else:
    print("Truthy")
Truthy

이 방식은 리스트뿐만 아니라 다양한 다른 객체에서도 작동합니다:

코드 보기
if not 0:
    print("Falsy")
else:
    print("Truthy")
Falsy
코드 보기
if not [0, 0, 0]:
    print("Falsy")
else:
    print("Truthy")
Truthy

0은 실수나 정수의 ’비어 있음’을 나타내므로 Falsy이지만, 세 개의 0이 들어 있는 리스트는 비어 있는 리스트가 아니므로 Truthy로 평가된다는 점에 유의하세요.

코드 보기
if not None:
    print("Falsy")
else:
    print("Truthy")
Falsy

무엇이 Truthy이고 무엇이 Falsy인지 아는 것은 실무에서 매우 유용합니다. 예를 들어 list_vals라는 리스트에 값이 없을 때 특정 동작을 수행하고 싶다면, 단순히 if list_vals를 사용하여 구현할 수 있습니다.

any()와 all()

불리언 데이터는 항상 혼자 나타나지는 않습니다. 그래서 any()all() 연산자가 존재합니다. 이 연산자들은 불리언 리스트와 같은 반복 가능한(iterables) 객체에 적용됩니다.

any()는 불리언 리스트 중 적어도 하나의 값이 참이면 참을 반환합니다:

코드 보기
any([True, False, False])
True

all()은 불리언 리스트의 모든 값이 참일 때만 참을 반환합니다:

코드 보기
all([True, True, True, True])
True

이 두 함수는 1과 0에 대해서도 작동합니다:

코드 보기
all([0, 0, 0, 1])
False

pandas 데이터 프레임에서의 불리언

데이터 프레임의 불리언 연산

데이터 프레임에서 True 또는 False 값을 가진 데이터를 다루는 시나리오를 자주 접하게 됩니다. pandas 데이터 프레임에서 불리언 열을 만드는 것은 쉽습니다:

코드 보기
import pandas as pd

df = pd.DataFrame.from_dict(
    {
        "bool_col_1": [False] * 3 + [True, True],
        "bool_col_2": [True, False, True, False, True],
    }
)
df
bool_col_1 bool_col_2
0 False True
1 False False
2 False True
3 True False
4 True True

일반적인 pandas 데이터 프레임 열과 마찬가지로 연산을 수행할 수 있습니다. 여기서는 & (and), | (or), == (equal), != (not equal) 연산자를 사용할 수 있습니다:

코드 보기
df["bool_col_1"] | df["bool_col_2"]
0     True
1    False
2     True
3     True
4     True
dtype: bool

참(True)인 값의 개수를 세는 것이 유용할 때가 많습니다. pandas 데이터 프레임에서 불리언 열의 합(sum)을 구하면 True 값의 총 개수를 계산해 줍니다:

코드 보기
df.sum()
bool_col_1    2
bool_col_2    3
dtype: int64

만약 데이터가 True/False가 아닌 1/0으로 되어 있다면, 데이터 타입을 변경하여 쉽게 변환할 수 있습니다:

코드 보기
df = pd.DataFrame.from_dict({"bool_col": [0, 1, 0, 1, 1]})
df["bool_col"].astype(bool)
0    False
1     True
2    False
3     True
4     True
Name: bool_col, dtype: bool

열 비교를 통한 불리언 생성

숫자 열(또는 다른 유형의 열)로부터 불리언 열을 생성할 수도 있습니다. 이를 증명하기 위해 diamonds 데이터셋을 사용해 보겠습니다:

코드 보기
diamonds = pd.read_csv(
    "https://github.com/mwaskom/seaborn-data/raw/master/diamonds.csv"
)
diamonds.head()
carat cut color clarity depth table price x y z
0 0.23 Ideal E SI2 61.5 55.0 326 3.95 3.98 2.43
1 0.21 Premium E SI1 59.8 61.0 326 3.89 3.84 2.31
2 0.23 Good E VS1 56.9 65.0 327 4.05 4.07 2.31
3 0.29 Premium I VS2 62.4 58.0 334 4.20 4.23 2.63
4 0.31 Good J SI2 63.3 58.0 335 4.34 4.35 2.75

가격(price)이 1000보다 큰 경우에 대한 새로운 불리언 변수를 생성해 보겠습니다.

코드 보기
diamonds["expensive"] = diamonds["price"] > 1000
diamonds.sample(10)
carat cut color clarity depth table price x y z expensive
25063 1.83 Very Good I VS1 61.9 57.0 13587 7.85 7.91 4.88 True
34331 0.41 Premium G SI1 62.8 58.0 861 4.74 4.72 2.97 False
35891 0.32 Ideal G IF 61.3 56.0 918 4.40 4.44 2.71 False
24028 1.11 Very Good D VVS1 62.5 58.0 12200 6.67 6.60 4.15 True
29235 0.43 Ideal G SI2 62.3 53.0 696 4.84 4.86 3.02 False
13727 0.27 Ideal E VS1 61.6 57.0 603 4.16 4.12 2.55 False
34678 0.42 Premium E SI1 61.2 60.0 873 4.86 4.81 2.96 False
15282 1.00 Ideal H VS2 61.2 60.0 6133 6.40 6.45 3.93 True
18836 1.53 Ideal J SI1 62.2 58.0 7721 7.35 7.37 4.58 True
19125 1.51 Very Good I SI1 63.1 56.0 7891 7.28 7.33 4.61 True

물론 이 작업은 assign 호출을 통해서도 가능합니다:

코드 보기
diamonds.assign(expensive=lambda x: x["price"] > 1000).head()
carat cut color clarity depth table price x y z expensive
0 0.23 Ideal E SI2 61.5 55.0 326 3.95 3.98 2.43 False
1 0.21 Premium E SI1 59.8 61.0 326 3.89 3.84 2.31 False
2 0.23 Good E VS1 56.9 65.0 327 4.05 4.07 2.31 False
3 0.29 Premium I VS2 62.4 58.0 334 4.20 4.23 2.63 False
4 0.31 Good J SI2 63.3 58.0 335 4.34 4.35 2.75 False

데이터 프레임에서 유용한 또 다른 불리언 활용법은 .isin() 함수입니다. 예를 들어, 특정 열 집합이 데이터 프레임에 있는지 여부를 나타내는 True/False 값을 얻고 싶을 때 사용합니다:

코드 보기
diamonds.columns.isin(["x", "y", "z"])
array([False, False, False, False, False, False, False,  True,  True,
        True, False])

데이터 프레임의 any()와 all()

불리언 타입의 pandas 열은 불리언 리스트와 매우 유사하게 작동하며, pandas 내장 메서드인 .any().all()을 통해 동일한 로직을 적용할 수 있습니다. "expensive" 항목 중 일부가 True일 것으로 예상되므로, any()는 True를 반환해야 합니다:

코드 보기
diamonds["expensive"].any()
np.True_

논리적 서브셋 (Logical subsetting)

지금까지 계속 사용해 왔지만 명확히 짚고 넘어가는 것이 좋습니다. 불리언은 데이터 프레임에서 논리적으로 부분 집합(subset)을 추출하는 데 사용될 수 있습니다. xy보다 큰 데이터 프레임의 부분만 원한다고 가정해 보겠습니다:

코드 보기
diamonds[diamonds["x"] > diamonds["y"]]
carat cut color clarity depth table price x y z expensive
1 0.21 Premium E SI1 59.8 61.0 326 3.89 3.84 2.31 False
8 0.22 Fair E VS2 65.1 61.0 337 3.87 3.78 2.49 False
11 0.23 Ideal J VS1 62.8 56.0 340 3.93 3.90 2.46 False
12 0.22 Premium F SI1 60.4 61.0 342 3.88 3.84 2.33 False
14 0.20 Premium E SI2 60.2 62.0 345 3.79 3.75 2.27 False
... ... ... ... ... ... ... ... ... ... ... ...
53928 0.79 Premium E SI2 61.4 58.0 2756 6.03 5.96 3.68 True
53929 0.71 Ideal G VS1 61.4 56.0 2756 5.76 5.73 3.53 True
53930 0.71 Premium E SI1 60.5 55.0 2756 5.79 5.74 3.49 True
53931 0.71 Premium F SI1 59.8 62.0 2756 5.74 5.73 3.43 True
53938 0.86 Premium H SI2 61.0 58.0 2757 6.15 6.12 3.74 True

23423 rows × 11 columns

diamonds["x"] > diamonds["y"] 표현식은 불리언 열을 생성하며, 이는 조건이 참인 행만 필터링하는 데 사용됩니다.