← Back to portfolio
Project · Machine Learning

B2B 영업기회 창출 예측 모델 개발

ML · Programming데이터 전처리 · 모델링

🪢 전체 Link

LG Aimers_MQL 데이터 기반 B2B 영업기회 창출 예측 모델 개발

🔎 상세 내용

고객 지수 산출을 위해 MQL 고객 정보를 활용해 영업 전환 성공 여부를 예측하는 AI 모델 개발

  1. 데이터 파악

    칼럼 30개, Y-True:1, False0, 텍스트 데이터: 요청-메시지 영어로 작성됨

  2. 데이터 전처리

    결측치 채우기, 범주형 변수 → 수치로 인코딩, 가공변수 생성

    • Ex 1. 특정 사업부 영역 칼럼 2개 → 1개의 가공변수로 통합

      id_strategic_ver (도메인 지식) 특정 사업부(Business Unit이 ID일 때), 특정 사업 영역(Vertical Level1)에 대해 가중치를 부여
      it_strategic_ver (도메인 지식) 특정 사업부(Business Unit이 IT일 때), 특정 사업 영역(Vertical Level1)에 대해 가중치를 부여

      칼럼 4. ID0.4 + IT0.6 = strategic_ver로 통합

      python (df_train["id_strategic_ver"].fillna(0)*0.4).value_counts()

      python 0.0 55855 0.4 3444 Name: id_strategic_ver, dtype: int64

      ```python

      새로운 컬럼 생성 및 계산

      df_train['strategic_ver']= df_train["id_strategic_ver"].fillna(0) * 0.4 + df_train['it_strategic_ver'].fillna(0) * 0.6

      df_train['strategic_ver'].value_counts()
      ```

      python 0.0 54734 0.4 3444 0.6 1121 Name: strategic_ver, dtype: int64

      python df_train.drop(["id_strategic_ver","it_strategic_ver"], axis=1, inplace=True) df_train.drop(['business_unit'], axis = 1, inplace = True) #어차피 strategic이랑 중복이니 삭제

      ```python

      test 적용

      df_test['strategic_ver']= df_test["id_strategic_ver"].fillna(0) * 0.4 + df_test['it_strategic_ver'].fillna(0) * 0.6

      df_test.drop(["id_strategic_ver","it_strategic_ver",'business_unit'], axis=1, inplace=True)

      df_train.columns #확인
      ```

      python Index(['bant_submit', 'com_reg_ver_win_rate', 'customer_type', 'enterprise', 'historical_existing_cnt', 'lead_desc_length', 'inquiry_type', 'product_category', 'expected_timeline', 'ver_cus', 'ver_pro', 'ver_win_rate_x', 'ver_win_ratio_per_bu', 'business_area', 'response_corporate', 'lead_owner', 'is_converted', 'strategic_ver'], dtype='object')

    • Ex 2. 국가별 코드 → 대륙별 코드로 가공변수 생성

      칼럼 7. response_corporate 전처리

      python unique_response_corporate = df_train['response_corporate'].unique() print(unique_response_corporate)

      python ['LGEPH' 'LGEIL' 'LGEAF' 'LGESJ' 'LGESL' 'LGESP' 'LGEGF' 'LGESA' 'LGEUS' 'LGECB' 'LGEMS' 'LGEEG' 'LGEEF' 'LGEAP' 'LGEIN' 'LGEUK' 'LGEKR' 'LGEPS' 'LGECI' 'LGECL' 'LGETK' 'LGELF' 'LGEPT' 'LGEPR' 'LGEDG' 'LGERO' 'LGEMK' 'LGEPL' 'LGECZ' 'LGEES' 'LGEAR' 'LGEHK' 'LGEML' 'LGEJP' 'LGEHS' 'LGEAS' 'LGEYK' 'LGEIS' 'LGEBN' 'LGEFS' 'LGESW' 'LGEMC' 'LGEAG' 'LGEEB' 'LGETH' 'LGEVH' 'LGECH' 'LGELA' 'LGETT' 'LGERA' 'LGEUR' 'LGEIR' 'LGEBT']

      ```python

      대륙별로 response_corporate 값을 매핑하는 딕셔너리 생성

      def map_to_continent(value):
      mapping = {
      'EU': ['LGEBN', 'LGEFS', 'LGEIS', 'LGEPH', 'LGEIL', 'LGEDG', 'LGEUK', 'LGEPS', 'LGETK', 'LGELF', 'LGEPT', 'LGEPR', 'LGERO', 'LGEMK', 'LGEPL', 'LGECZ', 'LGEES', 'LGEHS', 'LGEEB', 'LGETT'],
      'AS_PC': ['LGEPH', 'LGEIL', 'LGESL', 'LGEKR', 'LGEAP', 'LGEIN', 'LGEHK', 'LGEML', 'LGEJP', 'LGETH', 'LGEVH', 'LGECH'],
      'ME_AF': ['LGEAF', 'LGESJ', 'LGEGF', 'LGESA', 'LGEEG', 'LGEEF', 'LGETK', 'LGELF', 'LGEAS', 'LGEMC', 'LGEAR', 'LGEIR'],
      'N_AM': ['LGEUS', 'LGEMS', 'LGECI'],
      'L_AM': ['LGECL', 'LGESP', 'LGECB', 'LGEPS', 'LGECB', 'LGEMS', 'LGEMC', 'LGEAG', 'LGEPS', 'LGEEB', 'LGESW', 'LGEYK'],
      'R_CIS': ['LGERA', 'LGEUR', 'LGELA'],
      'Unknown': ['LGEBT']
      }

      for continent, companies in mapping.items():
          if value in companies:
              return continent
      return value
      

      ```

      ```python

      df_train DataFrame에서 expected_timeline 열의 모든 값을 문자열로 변환

      df_train['response_corporate'] = df_train['response_corporate'].astype(str)

      데이터프레임에 함수 적용

      df_train['response_corporate'] = df_train['response_corporate'].apply(map_to_continent)

      df_train['response_corporate'].value_counts()
      ```

      python EU 29037 L_AM 12846 N_AM 8986 ME_AF 5592 AS_PC 2826 R_CIS 11 Unknown 1 Name: response_corporate, dtype: int64

      ```python

      test 적용

      df_test['response_corporate'] = df_test['response_corporate'].astype(str)

      데이터프레임에 함수 적용

      df_test['response_corporate'] = df_test['response_corporate'].apply(map_to_continent)

      df_test['response_corporate'].value_counts() #확인
      ```

      python EU 1953 L_AM 1283 N_AM 1207 ME_AF 457 AS_PC 371 Name: response_corporate, dtype: int64

    • Ex 3. 원하는 수리 기간 → ‘기타’로 줄글로 작성된 의견들 통합

      칼럼 8. expected_timeline 전처리

      ```python

      expected timeline 문구 대체 시도

      0-3:0, 3-6:1, 6-9:2, 9-12:3, 12이상:4

      unique_expected_timeline = df_train['expected_timeline'].unique()

      대체할 값들

      replacement_values = {
      'less than 3 months': '0-3 months',
      '3 months ~ 6 months': '3-6 months',
      '6 months ~ 9 months': '6-9 months',
      '9 months ~ 1 year': '9 months - 1 year',
      'more than a year': 'more than 1 year'
      }

      비슷한 문구를 찾고 해당하는 값으로 대체하는 함수 정의

      def replace_similar_values(text):
      if isinstance(text, str):
      # 대소문자 및 공백을 무시하고 대체할 값 찾기
      for pattern, replacement in replacement_values.items():
      if re.search(re.compile(re.escape(pattern), re.IGNORECASE), text):
      return replacement
      # 대체할 값이 없는 경우 'etc'로 분류
      return 'etc'
      else:
      return 'etc' # NaN 또는 기타 형태의 값인 경우 'etc'로 분류

      ```

      ```python
      import re

      df_train DataFrame에서 expected_timeline 열의 모든 값을 문자열로 변환

      df_train['expected_timeline'] = df_train['expected_timeline'].astype(str)

      expected_timeline 열의 모든 값을 변경

      df_train['expected_timeline'] = df_train['expected_timeline'].apply(replace_similar_values)

      df_train['expected_timeline'].unique() #확인
      ```

      ```python

      test 적용

      df_test['expected_timeline'] = df_test['expected_timeline'].astype(str)

      expected_timeline 열의 모든 값을 변경

      df_test['expected_timeline'] = df_test['expected_timeline'].apply(replace_similar_values)

      print(df_test['expected_timeline'].value_counts()) #확인
      ```

      python etc 2408 0-3 months 1734 3-6 months 426 more than 1 year 272 9 months - 1 year 264 6-9 months 167 Name: expected_timeline, dtype: int64

  3. 레이블 인코딩
    전처리 시 수정한 컬럼, 새로 생성한 컬럼이 str 타입일 경우 실행
    - 코드

    ```python
    def label_encoding(series: pd.Series) -> pd.Series:
    """범주형 데이터를 시리즈 형태로 받아 숫자형 데이터로 변환합니다."""

    my_dict = {}
    
    # 모든 요소를 문자열로 변환
    series = series.astype(str)
    
    for idx, value in enumerate(sorted(series.unique())):
        my_dict[value] = idx
    series = series.map(my_dict)
    
    return series
    

    ```

    ```python

    str 변수 인코딩

    label_columns = [
    "business_area",
    'lead_desc_length',
    "customer_type",
    "enterprise",
    "inquiry_type",
    "response_corporate",
    "product_category",
    "expected_timeline",
    ]

    df_all = pd.concat([df_train[label_columns], df_test[label_columns]])

    for col in label_columns:
    df_all[col] = label_encoding(df_all[col])
    ```

    python for col in label_columns: df_train[col] = df_all.iloc[: len(df_train)][col] df_test[col] = df_all.iloc[len(df_train) :][col]

    ```python

    학습데이터셋, 평가 데이터셋 분류

    x_train, x_val, y_train, y_val = train_test_split(
    df_train.drop("is_converted", axis=1),
    df_train["is_converted"],
    test_size=0.2,
    shuffle=True,
    random_state=684050,
    )
    ```

Untitled

  1. 모델 실험

모델 성능 함수 정의

### 모델 성능 보기

def get_clf_eval(y_test, y_pred=None):
    confusion = confusion_matrix(y_test, y_pred, labels=[True, False])
    accuracy = accuracy_score(y_test, y_pred)
    precision = precision_score(y_test, y_pred, labels=[True, False])
    recall = recall_score(y_test, y_pred)
    F1 = f1_score(y_test, y_pred, labels=[True, False])

    print("오차행렬:\n", confusion)
    print("\n정확도: {:.4f}".format(accuracy))
    print("정밀도: {:.4f}".format(precision))
    print("재현율: {:.4f}".format(recall))
    print("F1: {:.4f}".format(F1))

오차행렬:
[[ 507 480]
[ 198 10675]]

정확도: 0.9428
정밀도: 0.7191
재현율: 0.5137
F1: 0.5993

오차행렬:
[[ 618 369]
[ 326 10547]]

정확도: 0.9414
정밀도: 0.6547
재현율: 0.6261
F1: 0.6401

오차행렬:
[[ 556 431]
[ 409 10464]]

정확도: 0.9292
정밀도: 0.5762
재현율: 0.5633
F1: 0.5697

오차행렬:
[[ 618 369]
[ 311 10562]]

정확도: 0.9427
정밀도: 0.6652
재현율: 0.6261
F1: 0.6451

오차행렬:
[[ 544 443]
[ 203 10670]]

정확도: 0.9455
정밀도: 0.7282
재현율: 0.5512
F1: 0.6275

→ Decisiontree val_data의 sum값이 가장 높게 나와서 decisiontree 하이퍼 파라미터 조정하는 걸로 시도

최종 제출

오차행렬:
[[ 618 369]
[ 313 10560]]

정확도: 0.9425
정밀도: 0.6638
재현율: 0.6261
F1: 0.6444

877

💡 깨달은 점