[{"data":1,"prerenderedAt":452},["ShallowReactive",2],{"lang-switch-post-\u002Fprojects\u002Folist-ecommerce\u002F03-features-e-selecao":3,"chapter-pt-olist-ecommerce-03-features-e-selecao":4},null,{"id":5,"title":6,"body":7,"cover":3,"date":438,"description":439,"extension":440,"meta":441,"navigation":57,"order":61,"path":442,"project":443,"seo":444,"status":445,"stem":446,"tags":447,"__hash__":451},"projectChapters\u002Fpt\u002Fprojects\u002Folist-ecommerce\u002F03-features-e-selecao.md","Vazamento de Dado: o Jeito Mais Fácil de Enganar a Si Mesmo",{"type":8,"value":9,"toc":430},"minimark",[10,22,27,35,101,116,120,157,172,175,178,194,197,200,212,216,225,234,303,311,315,318,372,375,379,382,412,419,423,426],[11,12,13,14,21],"p",{},"Os capítulos 1 e 2 montaram o dataframe mestre e mostraram seis ângulos exploratórios. Agora eu preciso escolher, pra cada um dos quatro cenários definidos lá no capítulo 1, quais features fazem sentido usar. Notebook completo, executado, ",[15,16,20],"a",{"href":17,"rel":18},"https:\u002F\u002Fcolab.research.google.com\u002Fdrive\u002F1nt9wVC9_2H646HEKG-XTo6P4cvx-zPPr?usp=sharing",[19],"nofollow","público no Colab",".",[23,24,26],"h2",{"id":25},"uma-feature-nova-primeiro-distância-cliente-vendedor","Uma feature nova primeiro: distância cliente-vendedor",[11,28,29,30,34],{},"O capítulo 2 mostrou que estado do cliente correlaciona com tempo de entrega. Antes de entrar nos cenários, calculei uma distância de verdade, não só \"mesmo estado ou não\": latitude\u002Flongitude média por prefixo de CEP (",[31,32,33],"code",{},"olist_geolocation_dataset",", mais de 1 milhão de linhas, por isso a média primeiro), e a fórmula de Haversine entre cliente e vendedor.",[36,37,42],"pre",{"className":38,"code":39,"language":40,"meta":41,"style":41},"language-python shiki shiki-themes github-light github-dark","geo_media = geolocation.groupby('geolocation_zip_code_prefix')[['geolocation_lat', 'geolocation_lng']].mean().reset_index()\n\ndef haversine(lat1, lon1, lat2, lon2):\n    R = 6371\n    phi1, phi2 = np.radians(lat1), np.radians(lat2)\n    dphi = np.radians(lat2 - lat1)\n    dlambda = np.radians(lon2 - lon1)\n    a = np.sin(dphi \u002F 2) ** 2 + np.cos(phi1) * np.cos(phi2) * np.sin(dlambda \u002F 2) ** 2\n    return 2 * R * np.arcsin(np.sqrt(a))\n","python","",[31,43,44,52,59,65,71,77,83,89,95],{"__ignoreMap":41},[45,46,49],"span",{"class":47,"line":48},"line",1,[45,50,51],{},"geo_media = geolocation.groupby('geolocation_zip_code_prefix')[['geolocation_lat', 'geolocation_lng']].mean().reset_index()\n",[45,53,55],{"class":47,"line":54},2,[45,56,58],{"emptyLinePlaceholder":57},true,"\n",[45,60,62],{"class":47,"line":61},3,[45,63,64],{},"def haversine(lat1, lon1, lat2, lon2):\n",[45,66,68],{"class":47,"line":67},4,[45,69,70],{},"    R = 6371\n",[45,72,74],{"class":47,"line":73},5,[45,75,76],{},"    phi1, phi2 = np.radians(lat1), np.radians(lat2)\n",[45,78,80],{"class":47,"line":79},6,[45,81,82],{},"    dphi = np.radians(lat2 - lat1)\n",[45,84,86],{"class":47,"line":85},7,[45,87,88],{},"    dlambda = np.radians(lon2 - lon1)\n",[45,90,92],{"class":47,"line":91},8,[45,93,94],{},"    a = np.sin(dphi \u002F 2) ** 2 + np.cos(phi1) * np.cos(phi2) * np.sin(dlambda \u002F 2) ** 2\n",[45,96,98],{"class":47,"line":97},9,[45,99,100],{},"    return 2 * R * np.arcsin(np.sqrt(a))\n",[11,102,103,104,107,108,111,112,115],{},"Achei um bug de verdade nessa etapa antes de publicar: na primeira rodada, o merge entre ",[31,105,106],{},"customer_zip_code_prefix","\u002F",[31,109,110],{},"seller_zip_code_prefix"," e a tabela de geolocalização perdeu 70% dos dados (só 30% de cobertura), porque o tipo da coluna de CEP não veio garantidamente igual dos dois lados em todo ambiente. Forcei ",[31,113,114],{},"int64"," explícito nos três antes do merge, e a cobertura voltou pro esperado: só 581 pedidos de 118.310 ficaram sem distância (99,5% de cobertura). A distância média cliente-vendedor no Brasil inteiro é 597 km, com máximo de 8.678 km, uma prova em número de como o país é gigante.",[23,117,119],{"id":118},"cenário-1-atraso-na-entrega-e-a-demonstração-de-vazamento-de-dado","Cenário 1: Atraso na entrega, e a demonstração de vazamento de dado",[11,121,122,123,126,127,126,130,126,133,136,137,140,141,144,145,148,149,152,153,156],{},"Candidatas: ",[31,124,125],{},"distance_km",", ",[31,128,129],{},"product_weight_g",[31,131,132],{},"price",[31,134,135],{},"freight_value",", tempo de aprovação (",[31,138,139],{},"approval_hours","), mês da compra. O alvo ",[31,142,143],{},"atrasado"," é definido comparando ",[31,146,147],{},"order_delivered_customer_date"," com ",[31,150,151],{},"order_estimated_delivery_date",". E aqui mora a armadilha clássica de todo curso de ML: se eu incluir ",[31,154,155],{},"atraso_dias"," (a própria diferença entre essas duas datas) como feature, o modelo não aprende a prever atraso, ele lê a resposta direto da prova.",[36,158,160],{"className":38,"code":159,"language":40,"meta":41,"style":41},"features_honestas = ['distance_km', 'product_weight_g', 'price', 'freight_value', 'approval_hours', 'month']\nfeatures_vazadas = features_honestas + ['atraso_dias']\n",[31,161,162,167],{"__ignoreMap":41},[45,163,164],{"class":47,"line":48},[45,165,166],{},"features_honestas = ['distance_km', 'product_weight_g', 'price', 'freight_value', 'approval_hours', 'month']\n",[45,168,169],{"class":47,"line":54},[45,170,171],{},"features_vazadas = features_honestas + ['atraso_dias']\n",[11,173,174],{},"Treinei os dois, RandomForest simples, mesma seed, mesma divisão treino\u002Fteste:",[176,177],"olist-leakage-comparison-chart",{},[11,179,180,181,185,186,189,190,193],{},"O modelo honesto chega em ",[182,183,184],"strong",{},"AUC 0,7388"," (95.968 pedidos usados, taxa real de atraso de 6,76%). O modelo vazado chega em ",[182,187,188],{},"AUC 1,0",", perfeito, porque ",[31,191,192],{},"atraso_dias > 0"," praticamente já É a definição do alvo. Um detalhe que vale destacar: a acurácia do modelo honesto sozinha (93,24%) parece ótima à primeira vista, mas como só 6,76% dos pedidos atrasam, um modelo bobo que sempre chuta \"no prazo\" já acerta 93,24% sem aprender nada. Acurácia sozinha engana quando as classes são desbalanceadas desse jeito, AUC é a métrica que conta a história real aqui.",[11,195,196],{},"Repara também a importância de feature do modelo honesto:",[198,199],"olist-feature-importance-chart",{},[11,201,202,208,209,211],{},[182,203,204,207],{},[31,205,206],{},"month"," lidera disparado, com 0,36 de importância",", praticamente o dobro da segunda colocada (",[31,210,125],{},", 0,18). Isso conecta direto com o achado do capítulo 2: novembro (Black Friday) sobrecarrega a logística de um jeito que nem distância física consegue explicar sozinha. Sazonalidade importa mais pra prever atraso do que o quão longe o vendedor está do cliente.",[23,213,215],{"id":214},"cenário-2-nota-da-avaliação","Cenário 2: Nota da avaliação",[11,217,122,218,220,221,224],{},[31,219,155],{},", tempo de entrega, preço, frete, número de parcelas. Como ",[31,222,223],{},"review_score"," é uma escala ordinal bimodal (o capítulo 2 já mostrou isso), usei informação mútua em vez de correlação de Pearson simples:",[36,226,228],{"className":38,"code":227,"language":40,"meta":41,"style":41},"mi = mutual_info_classif(X2, y2, random_state=42)\n",[31,229,230],{"__ignoreMap":41},[45,231,232],{"class":47,"line":48},[45,233,227],{},[235,236,237,252],"table",{},[238,239,240],"thead",{},[241,242,243,248],"tr",{},[244,245,247],"th",{"align":246},"left","Feature",[244,249,251],{"align":250},"right","Informação mútua",[253,254,255,265,275,284,293],"tbody",{},[241,256,257,262],{},[258,259,260],"td",{"align":246},[31,261,155],{},[258,263,264],{"align":250},"0,0684",[241,266,267,272],{},[258,268,269],{"align":246},[31,270,271],{},"delivery_days",[258,273,274],{"align":250},"0,0571",[241,276,277,281],{},[258,278,279],{"align":246},[31,280,135],{},[258,282,283],{"align":250},"0,0077",[241,285,286,290],{},[258,287,288],{"align":246},[31,289,132],{},[258,291,292],{"align":250},"0,0074",[241,294,295,300],{},[258,296,297],{"align":246},[31,298,299],{},"payment_installments",[258,301,302],{"align":250},"0,0026",[11,304,305,307,308,310],{},[31,306,155],{}," e ",[31,309,271],{}," dominam, quase 10 vezes mais informativas que preço ou frete (95.829 pedidos usados). Confirma com uma técnica diferente exatamente o que a correlação do capítulo 2 já tinha apontado.",[23,312,314],{"id":313},"cenário-3-valor-do-frete","Cenário 3: Valor do frete",[11,316,317],{},"Candidatas: peso e as três dimensões do produto. Correlação de Pearson simples já resolve aqui (98.650 pedidos usados):",[235,319,320,331],{},[238,321,322],{},[241,323,324,326],{},[244,325,247],{"align":246},[244,327,328,329],{"align":250},"Correlação com ",[31,330,135],{},[253,332,333,342,352,362],{},[241,334,335,339],{},[258,336,337],{"align":246},[31,338,129],{},[258,340,341],{"align":250},"0,615",[241,343,344,349],{},[258,345,346],{"align":246},[31,347,348],{},"product_height_cm",[258,350,351],{"align":250},"0,393",[241,353,354,359],{},[258,355,356],{"align":246},[31,357,358],{},"product_width_cm",[258,360,361],{"align":250},"0,331",[241,363,364,369],{},[258,365,366],{"align":246},[31,367,368],{},"product_length_cm",[258,370,371],{"align":250},"0,317",[11,373,374],{},"Peso domina moderadamente sobre qualquer dimensão isolada, o que faz sentido físico: transportadora cobra por peso volumétrico, mas peso real puxa mais a conta que qualquer lado da caixa sozinho.",[23,376,378],{"id":377},"cenário-4-segmentação-de-clientes-rfm","Cenário 4: Segmentação de clientes (RFM)",[11,380,381],{},"Aqui não é seleção de feature no sentido supervisionado, é a engenharia das três variáveis que vão alimentar o clustering do capítulo 5: Recência, Frequência e Valor monetário por cliente.",[36,383,385],{"className":38,"code":384,"language":40,"meta":41,"style":41},"rfm = oc.groupby('customer_unique_id').agg(\n    recencia_dias=('order_purchase_timestamp', lambda x: (data_max - x.max()).days),\n    frequencia=('order_id', 'nunique'),\n    valor_monetario=('payment_value', 'sum'),\n).reset_index()\n",[31,386,387,392,397,402,407],{"__ignoreMap":41},[45,388,389],{"class":47,"line":48},[45,390,391],{},"rfm = oc.groupby('customer_unique_id').agg(\n",[45,393,394],{"class":47,"line":54},[45,395,396],{},"    recencia_dias=('order_purchase_timestamp', lambda x: (data_max - x.max()).days),\n",[45,398,399],{"class":47,"line":61},[45,400,401],{},"    frequencia=('order_id', 'nunique'),\n",[45,403,404],{"class":47,"line":67},[45,405,406],{},"    valor_monetario=('payment_value', 'sum'),\n",[45,408,409],{"class":47,"line":73},[45,410,411],{},").reset_index()\n",[11,413,414,415,418],{},"95.560 clientes únicos. E o número que mais chamou atenção: ",[182,416,417],{},"só 2.924 clientes (3,06% do total) fizeram mais de um pedido",". A frequência média é 1,034, ou seja, a marketplace inteira é dominada por compra única. Isso muda completamente como pensar em segmentação no capítulo 5: não vai ter \"cliente fiel\" como categoria grande, o RFM aqui provavelmente separa mais por valor gasto e recência do que por frequência de compra.",[23,420,422],{"id":421},"fechando-o-capítulo","Fechando o capítulo",[11,424,425],{},"Distância cliente-vendedor calculada (e um bug de merge real corrigido no processo), vazamento de dado demonstrado na prática (a diferença entre AUC 0,74 honesto e AUC 1,0 vazado é a lição mais concreta que dá pra dar sobre o assunto), e as features de cada cenário definidas com técnica apropriada pro tipo de problema. Sazonalidade se provou mais importante que distância pro atraso, atraso domina a nota de avaliação, peso domina o frete, e a base de clientes é majoritariamente de compra única. No próximo capítulo eu treino os modelos de verdade pra cada cenário, com tracking de experimento e comparação de métrica.",[427,428,429],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":41,"searchDepth":54,"depth":54,"links":431},[432,433,434,435,436,437],{"id":25,"depth":54,"text":26},{"id":118,"depth":54,"text":119},{"id":214,"depth":54,"text":215},{"id":313,"depth":54,"text":314},{"id":377,"depth":54,"text":378},{"id":421,"depth":54,"text":422},"2026-08-24","Terceiro capítulo do case Olist: engenharia de feature e seleção pros quatro cenários, com uma demonstração ao vivo de vazamento de dado (um modelo com 100% de AUC que não serve pra nada) e uma descoberta real sobre sazonalidade.","md",{},"\u002Fpt\u002Fprojects\u002Folist-ecommerce\u002F03-features-e-selecao","olist-ecommerce",{"title":6,"description":439},"published","pt\u002Fprojects\u002Folist-ecommerce\u002F03-features-e-selecao",[448,449,450],"scikit-learn","feature-engineering","data-leakage","OtCRzHZzopXyFfeHmBNYS52rdEXk2SkORG7YuL1nHpM",1787605215741]