Criação de ficheiro Excel com Python

Na jornada da análise de dados, a extração e organização de informações valiosas é crucial para embasar decisões estratégicas. Ao trabalhar com dados armazenados em bases de dados SQL, surge a necessidade de extrair esses dados e visualizá-los de maneira mais acessível e compreensível.

Nesse cenário, o Python destaca-se com sua biblioteca Pandas, oferecendo recursos poderosos para manipulação e análise de dados. A função to_excel do Pandas e a classe ExcelWriter são ferramentas essenciais nesse processo.

A função to_excel é uma funcionalidade simples e direta do Pandas, permitindo a exportação fácil e rápida de DataFrames para arquivos Excel. Com apenas algumas linhas de código é possível transformar conjuntos de dados complexos em planilhas estruturadas e visualmente atrativas.

Por outro lado, a classe ExcelWriter oferece um controle mais granular sobre a formatação do arquivo Excel. Ela permite a criação de arquivos mais elaborados, possibilitando a definição de configurações específicas, como formatação de células, múltiplos separadores, estilos e muito mais.

Essas ferramentas tornam-se fundamentais para profissionais e equipas que pretendem criar relatórios detalhados, dashboards ou compartilhar resultados de análises de dados de forma acessível e amigável para diferentes stakeholders.

Além disso, a capacidade de exportar dados SQL diretamente para um arquivo Excel simplifica o processo de comunicação entre equipas, facilitando a compreensão e a interpretação dos dados por partes interessadas que não estão familiarizadas com linguagens de consulta de base de dados.

Em resumo, a função to_excel e a classe ExcelWriter são recursos valiosos do Pandas que desempenham um papel crucial na transformação de dados SQL em visualizações ricas e formatadas em Excel. Essas ferramentas simplificam o processo de geração de relatórios e fortalecem a comunicação de insights vitais derivados da análise de dados.

Ver todo o script.

Espero que vos seja útil!

Bom ano de 2024!

Classificação de títulos de bolsa com Clustering

O seguinte trabalho pretende fazer a Classificação de títulos de bolsa nacionais e internacionais com base no algoritmo K-Means, um problema de classificação não supervisionada (clustering) que agrupa objetos em “k” número de grupos baseados nas suas características.

O intuito deste trabalho é encontrar as melhores ações de bolsa a nível “mundial”, através dos seus indicadores de Retorno e Volatilidade anuais, com  base no histórico dos últimos dois anos. Termino com uma seleção de títulos “apetecíveis” especificando os seus valores atuais em bolsa.

O trabalho passa por diferentes métodos de Data Science e é todo realizado em Python:

  • obtenção de dados da web através de Web Scrapping,
  • tratamento dos mesmos em Pandas,
  • aplicação do modelo de classificação K-means, começando por procurar o melhor do número de clusters (“k”),
  • remoção de outliers,
  • seleção das melhores ações com retorno anual igual ou acima de 10% e volatilidade abaixo de 40% (escolha pessoal!),
  • obtenção dos preços atuais dos títulos da alínea anterior,
  • amostragem final das “melhores” ações em diferentes colorações consoante o seu preço

Elbow curve – escolha do número de clusters para o algoritmo k-means (k=3):Picture5

Classificação de títulos implementando 3 Clusters face ao Retorno e Volatilidade anuais:
Picture1

Remoção de outliers:
Picture2

Títulos com Rentabilidade acima de 10% ao ano e Volatilidade inferior a 40%:Picture3

Seleção de títulos com preços abaixo de 50€:Picture4

O script Python (jupyter) está disponível no GitHub, sendo esta a conclusão final:

  • valor até 5€: o título EGL.LS é o que mostra maior retorno com uma taxa de volatilidade abaixo dos 35%. Logo de seguida o BCP.LS
  • inferior a 10€: o título BBVA.MC tem um retorno acima de 30% e volatilidade abaixo dos 30%
  • inferior a 20€ ou 50€: se optarmos por um título mais estável, abaixo dos 20% de volatilidade, temos a SEM.LS (inf20) e o DTE.DE (inf50)

Acesso ao trabalho completo no GitHub


Introducing the k-means algorithm
The term k-means was first used by MacQueen in 1967, although the idea dates back to Steinhaus in 1957. K-means is an unsupervised classification (clustering) algorithm that groups objects into k groups based on their characteristics.

Clustering is done by minimizing the sum of distances between each object and the centroid of its group or cluster. Quadratic distance is often used. The algorithm consists of three steps:

Initialization: once the number of groups, k, has been chosen, k centroids are established in the data space, for example, choosing them randomly.
Assign objects to centroids: each data object is assigned to its nearest centroid.
Centroid update: the position of the centroid of each group is updated, taking as the new centroid the position of the average of the objects belonging to said group.
Steps 2 and 3 are repeated until the centroids do not move, or move below a threshold distance at each step.

Como copiar tabelas de Websites para o Pandas

Olá,

num projecto recente de previsão de séries temporais fui dirigido para um determinado tema que, de uma forma muito simples, pode “copiar” tabelas de Websites para o Python (Pandas).

Já havia trabalhado com Pandas e SQL, ou CSV, mas conseguir dados de html só mesmo com algo mais profundo na área de Web Scraping…

Para o caso aqui é muito mais simples, basta utilizar a função read_html() .

Esta função consegue ler tudo que seja <table> em html e importar para o Pandas.

Para teste poderemos utilizar a página “https://en.wikipedia.org/wiki/Python_(programming_language)“.

Script Python:

import pandas as pd
url = ‘_https://en.wikipedia.org/wiki/Python_(programming_language)’

pd.read_html(url)

Com isto, o Pandas importa todas as tabelas que encontrar na página html, separando-as por vírgula. De seguida, deveremos concentrar o pedido na tabela desejada, invocando a sua posição no vector, neste caso quero a segunda tabela (os índices começam em zero).

python_types = pd.read_html(url)[1]
python_types

Por fim, se der jeito, poderemos exportar esta tabela para Excel:

python_types.to_excel(r’python_types.xlsx’, sheet_name=’python_types’, index=False)

Este foi apenas um caso simples para utilização desta função do Pandas. Claro que pode ser muito mais útil e complexo se pretendermos agora juntar duas ou mais tabelas antes de exportar para Excel.

Espero que vos seja útil,

JG

Como desligar temporariamente uma Power App?

Olá,
uma das ferramentas com que lido diariamente são as Power Apps.
Em determinada altura surgiu a necessidade de colocar uma dessas aplicações em estado inacessível. O propósito prende-se com a necessidade da app ficar disponível apenas duas semanas por mês, evitando alterações aos dados nas outras semanas.

Na procura por algo que ajudasse, eis que surgiu a resposta na net: “How to turn app off temporarily?

“…Model-Driven Apps can be Deactivated via the maker portal. For Canvas Apps, there is no deactivate option unfortunately, so your best bet would be to modify the app permissions so that only yourself/admins can access it. For an On-Premise Data Gateway, you can similarly modify the permissions so only admins have access. The other option is to stop and disable the Gateway service on your servers where the gateway is installed… ”

Alterar permissões ou definições do gateway pode significar problemas noutras apps, além da necessária reconfiguração para ativar/inativar a app.

Algumas formas de abordar a inativação da app sem recorrer à solução anterior poderiam ser estas:

1) Alterar ao nível do SQL: se a app se basear numa tabela temporária onde possam ser eliminados ou alteradas permissões aos dados até à próxima atualização seria uma possibilidade – o utilizador acederia à app e não poderia fazer alterações por não ver registos ou ter apenas acesso de visualização.
2) Alterar ao nível da app: no local de edição dos registos colocar o modo “DisplayMode.Disabled” ou “DisplayMode.View”, em detrimento de “DisplayMode.Edit”. Para automatizar este processo poder-se-ía colocar o seguinte código do campo “DisplayMode” da galeria onde estão os registos editáveis:

If(Today()<=Date(Year(Now()), Month(Now()), 15),DisplayMode.Edit,DisplayMode.Disabled)

O código anterior coloca em modo de edição caso o dia actual seja inferior ao dia 16, no caso do dia ser maior ou igual a 16 os registos ficam impossibilitados de edição.

Ambas as soluções permitem de facto evitar edição dos dados a partir de determinado momento mas não são elegantes e muito menos facilmente configuráveis – o utilizador conseguirá aceder sempre à aplicação e tentará, com certeza, clicar e editar os registos. Mesmo colocando uma mensagem de aviso haveria um sentimento de que a app estaria com erros de edição.

Uma forma mais elegante e que não deixará em dúvida o utilizador, será mostrar uma mensagem no acesso à app especificando que a mesma está bloqueada (porque ultrapassou determinada data) não havendo acesso ao seu interface gráfico de edição.

Esta será uma solução mais intuitiva e simples, e passará pelos seguintes passos:

  • Criar “New Screen” ou replicar um existente por forma a manter as formatações:
  • Alterar o nome do novo screen, optei por “MessageScreen”
  • Colocar no novo screen o que achar interessante para mostrar uma mensagem – no caso foram icons e labels:
  • Por fim, no objeto “App”, alterar o “StartScreen” colocando o seguinte código:
    If(Today()>=Date(Year(Now()), Month(Now()), 16),MessageScreen,BrowseScreen1)

    Este código permite aceder ao screen de edição da app (“BrowseScreen1” ) até ao dia 15 de cada mês,inclusivé. Após esse dia será redirecionado para o screen “MessageScreen” onde apenas verá uma mensagem com aviso de indisponibilidade, com possibilidade de fecho da aplicação.

Forma muito rápida, simples e intuitiva de colocar uma app fora de serviço temporariamente.

Espero que vos seja útil,

JG

Fragmentação Índices SQL – Resolução

Olá,
o tema que trago aqui hoje, embora estranho para alguns, é de enorme importância na manutenção da performance aos dados no SQL Server. Trata-se da fragmentação que os índices das tabelas sofrem provocando lentidão no acesso aos dados.

A fragmentação dos índices é provocada por diversos factores, em tabelas onde existem muitos deletes, updates ou inserts o assunto é uma constante. Outra ação que leva à fragmentação é a compactação das DBs, sendo o mundo ideal nunca fazer shrink às mesmas.

O facto é que todos temos de realizar estas ações mais cedo ou mais tarde, porque a DB cresceu exponencialmente com alguma transação, ou porque o disco de repente ficou cheio e urge compactar a DB, ou simplesmente porque se eliminam dados e se inserem novos num contexto permanente. A solução estará na desfragmentação dos índices, quer seja através da reorganização (Reorganize) ou da reconstrução (Rebuild).

O script que vos trago permite realizar a verificação da fragmentação em determinada (s) DBs e, caso a percentagem de fragmentação seja superior a 5%, realizar uma das duas ações:

* Reorganize, caso a fragmentação esteja entre 5% e 30%
* Rebuil, caso a fragmentação seja superior a 30%

Os scripts são baseados em Cursores percorrendo todas as DBs necessárias. Por acaso apenas especifico uma DB por questão de rapidez.

O código utiliza a função Rownumber() para escolher as linhas de índices únicas. Podem existir diversos índices para a mesma tabela, mas como faço o Reorganize ALL/Rebuild ALL devo apelar à unicidade – caso contrário estaria a tratar mais que uma vez o mesmo índice.

/*--------------------------------------------
				INDEX REORGANIZE
		fragmentation between 5 and 30
----------------------------------------------*/


DECLARE @Database NVARCHAR(255)   
DECLARE @Table NVARCHAR(4000)  
DECLARE @cmd NVARCHAR(1000)  

DECLARE DatabaseCursor CURSOR READ_ONLY FOR  
SELECT name FROM master.sys.databases   
--WHERE name NOT IN ('master','msdb','tempdb','model','Testes')  -- databases to exclude
WHERE name IN ('DB_User')		-- specific databases
AND state = 0					                    -- database is online
AND is_in_standby = 0			-- database is not read only for log shipping
ORDER BY 1  

OPEN DatabaseCursor  

FETCH NEXT FROM DatabaseCursor INTO @Database  
WHILE @@FETCH_STATUS = 0  
BEGIN  

	SET @cmd = 'DECLARE TableCursor CURSOR READ_ONLY FOR 
			select tableName from (
				SELECT ''['' + table_catalog + ''].['' + table_schema + ''].['' + table_name + '']'' as tableName
				--,I.name as ''IndexName'',DDIPS.avg_fragmentation_in_percent 
				, row_number() over (partition by T.name order by I.name) As nlinha  
				FROM [' + @Database + '].INFORMATION_SCHEMA.TABLES A
				INNER JOIN sys.tables T ON A.TABLE_NAME = T.NAME
				INNER JOIN sys.dm_db_index_physical_stats (DB_ID(), NULL, NULL, NULL, NULL) DDIPS ON T.object_id = DDIPS.object_id   
				INNER JOIN sys.schemas S ON T.schema_id = S.schema_id
				INNER JOIN sys.indexes I ON I.object_id = DDIPS.object_id AND DDIPS.index_id = I.index_id
				WHERE 
					DDIPS.database_id = DB_ID() 
					AND I.name is not null 
					AND DDIPS.avg_fragmentation_in_percent  between 5 and 30   
					AND A.table_type = ''BASE TABLE''
					) kk
			where nlinha=1'     
		
   -- create table cursor  
   EXEC (@cmd)  
   --PRINT(@cmd)
   OPEN TableCursor   

   FETCH NEXT FROM TableCursor INTO @Table   
   WHILE @@FETCH_STATUS = 0   
   BEGIN
      BEGIN TRY   
         SET @cmd = 'ALTER INDEX ALL ON ' + @Table + ' REORGANIZE' 
         PRINT @cmd 				
         EXEC (@cmd) 
      END TRY
      BEGIN CATCH
         PRINT '---'
         PRINT @cmd
         PRINT ERROR_MESSAGE() 
         PRINT '---'
      END CATCH

      FETCH NEXT FROM TableCursor INTO @Table   
   END   

   CLOSE TableCursor   
   DEALLOCATE TableCursor  

   FETCH NEXT FROM DatabaseCursor INTO @Database  
END  
CLOSE DatabaseCursor   
DEALLOCATE DatabaseCursor

Agora o código para o REBUILD dos índices:


/*--------------------------------------------
				INDEX REBUILD
		case fragmentation > 30
----------------------------------------------*/


DECLARE @Database NVARCHAR(255)   
DECLARE @Table NVARCHAR(4000)  
DECLARE @cmd NVARCHAR(1000)  

DECLARE DatabaseCursor CURSOR READ_ONLY FOR  
SELECT name FROM master.sys.databases   
--WHERE name NOT IN ('master','msdb','tempdb','model','Testes')  -- databases to exclude
WHERE name IN ('DB_User')				-- specific databases
AND state = 0							                    -- database is online
AND is_in_standby = 0					-- database is not read only for log shipping
ORDER BY 1  

OPEN DatabaseCursor  

FETCH NEXT FROM DatabaseCursor INTO @Database  
WHILE @@FETCH_STATUS = 0  
BEGIN  

   SET @cmd = 'DECLARE TableCursor CURSOR READ_ONLY FOR 
			select tableName from (
				SELECT ''['' + table_catalog + ''].['' + table_schema + ''].['' + table_name + '']'' as tableName
				--,I.name as ''IndexName'',DDIPS.avg_fragmentation_in_percent 
				, row_number() over (partition by T.name order by I.name) As nlinha  
				FROM [' + @Database + '].INFORMATION_SCHEMA.TABLES A
				INNER JOIN sys.tables T ON A.TABLE_NAME = T.NAME
				INNER JOIN sys.dm_db_index_physical_stats (DB_ID(), NULL, NULL, NULL, NULL) DDIPS ON T.object_id = DDIPS.object_id   
				INNER JOIN sys.schemas S ON T.schema_id = S.schema_id
				INNER JOIN sys.indexes I ON I.object_id = DDIPS.object_id AND DDIPS.index_id = I.index_id
				WHERE 
					DDIPS.database_id = DB_ID() 
					AND I.name is not null 
					AND DDIPS.avg_fragmentation_in_percent  > 30 --or T.name = ''STOCK''   
					AND A.table_type = ''BASE TABLE''
					) kk
			where nlinha=1'     
		
   -- create table cursor  
   EXEC (@cmd)  
   --PRINT(@cmd)
   OPEN TableCursor   

   FETCH NEXT FROM TableCursor INTO @Table   
   WHILE @@FETCH_STATUS = 0   
   BEGIN
      BEGIN TRY   
         SET @cmd = 'ALTER INDEX ALL ON ' + @Table + ' REBUILD' 
         PRINT @cmd 				
         EXEC (@cmd) 
      END TRY
      BEGIN CATCH
         PRINT '---'
         PRINT @cmd
         PRINT ERROR_MESSAGE() 
         PRINT '---'
      END CATCH

      FETCH NEXT FROM TableCursor INTO @Table   
   END   

   CLOSE TableCursor   
   DEALLOCATE TableCursor  

   FETCH NEXT FROM DatabaseCursor INTO @Database  
END  
CLOSE DatabaseCursor   
DEALLOCATE DatabaseCursor

Por fim, estes dois scripts podem ser inseridos em dois steps num único job SQL e feito o agendamento para ser executado durante a noite. E se durante a noite existirem processos de alteração de dados este script deverá correr após isso por forma a manter a boa performance para o próximo dia de trabalho.

Espero que vos seja útil,

JG

Calendário com Moda Estatística em SQL

Olá,
este artigo sendo geral aplica-se , principalmente, à área de vendas a retalho onde o horário de abertura e de fecho das lojas é fundamental para, nomeadamente, o cálculo de objectivos de vendas.

Todos os meses será necessário o cálculo de objectivos de vendas para cada loja, sendo necessário planear o calendário de abertura/fecho para cada loja, principalmente nesta altura com tantas alterações repentinas por causa do COVID.

As ferramentas de suporte ao negócio apenas podem ajudar efectivamente o negócio após terem sido definidos os calendários mensais de cada loja. Esta acção deve ser desenvolvida pelos gerentes ou supervisores de lojas uma vez serem eles os detentores do conhecimento local.

A tarefa pode ser facilitada se lhes for fornecido um calendário pré-preenchido, ficando a seu encargo a validação e alguma alteração esporádica.

Este processo pode ser baseado nos horários do mês anterior, se determinada loja tem um horário “x” este mês será provável que para o próximo mês se mantenha.
Mas aqui a proposta vai ainda mais longe, vamos basear o cálculo dos horários futuros na maior frequência de horário do mês anterior, isto é, na moda, para cada dia da semana.

Para se perceber melhor examinemos o seguinte cenário:
Tomemos por base as sextas-feiras deste mês, para a loja nº1. Existem 4 sextas, com horário de abertura igual (open) e horário de fecho diferente (close).

Percebe-se que a maior frequência (Moda estatística) para a hora de fecho é às 22 horas. Será este valor que deve ficar registado no horário de fecho das sextas-feiras do próximo mês a calcular (Junho). O cálculo para todos os outros dias de semana, para todas as lojas, é semelhante.

Iniciemos por analisar a tabela do calendário para o mês de Maio e, para facilitar, apenas para a loja nº 11:

O weekday 1 decidiu-se ser a segunda-feira.

Vamos começar por calcular a Moda da hora de abertura do mês corrente e, para simplificar, apenas para a loja 11:

A coluna contador faz a contagem do número de vezes que cada hora de abertura está presente. A coluna open_serial_rank, baseada na partição por store/weekday, faz também uma contagem colocando o valor mais baixo para a contagem mais alta, isto é, o número mais frequente (maior valor em contador) é o que tem valor 1 (menor valor em open_serial_rank).

Repare-se por exemplo no weekday=4, tem 3 horários de entrada às 10 horas e 1 horário às 24 (na prática está fechado aqui!) – a Moda será 10. Já no weekday=7, o valor mais frequente para o horário de abertura é 24, isto é, a loja esteve maioritariamente fechada – será este estado a reflectir em Junho.

Apliquemos, agora, o mesmo processo para o cálculo da hora de fecho:

Repare-se por exemplo no weekday = 4 com 3 contagens de hora de fecho às 22 e uma contagem às 24 (fechado) – a Moda será 22, conforme especificado por close_serial_rank = 1.

Os valores da Moda serão os que se devem colocar posteriormente no calendário de Junho.

Agora basta juntar os dois códigos e obteremos a hora de abertura e a hora de fecho com mais frequência, isto é, onde open_serial_rank = 1 e close_serial_rank = 1:

/*
MODA ESTATÍSTICA SQL,
2021 JG
*/
select * from 
(
select  
	store,
	weekday, 
	open_serial,
	count(*) contador, 
	row_number() over (partition by store,weekday order by count(*) desc) open_serial_rank
from [dbo].[CALENDARIO_LOJAS] (NOLOCK)
where 
		date between '2021-05-01' and '2021-05-31'
		and store=11		
group by store, weekday, open_serial 		
) KK
INNER JOIN
(
select  
	store AS STORE_, 
	weekday AS WEEKDAY_, 
	close_serial, 
	count(*) CONTADOR_, 
	row_number() over (partition by store,weekday order by count(*) desc) close_serial_rank
from [dbo].[CALENDARIO_LOJAS] (NOLOCK)
where 
		date between '2021-05-01' and '2021-05-31'
		and store=11		
group by store, weekday, close_serial
) JJ 
on KK.store = JJ.STORE_ and KK.weekday = JJ.WEEKDAY_
where open_serial_rank=1 and close_serial_rank=1

Resultado:

Para cada um dos dias de semana ([1..7]) teremos agora a Moda de abertura (open_serial) e a Moda de fecho (close_serial). No Domingo, correspondente ao weekday 7, a loja estará fechada.

Para finalizar, basta cruzar este código com a tabela calendário para o mês futuro, Junho, assumindo as colunas de hora de abertura e fecho como sendo open_moda e close_moda, respectivamente:

/*
MODA ESTATÍSTICA SQL,
2021 JG
*/

select  HH.store,
	HH.date,
	HH.weekday,
	DD.open_serial AS open_moda, 
	DD.close_serial AS close_moda 
from [dbo].[CALENDARIO_LOJAS] (NOLOCK) HH
LEFT JOIN
(
	select * from 
	(
	select  
		store,
		weekday, 
		open_serial,
		count(*) contador, 
		row_number() over (partition by store,weekday order by count(*) desc) open_serial_rank
	from [dbo].[CALENDARIO_LOJAS] (NOLOCK)
	where 
			date between '2021-05-01' and '2021-05-31'
			and store=11		
	group by store, weekday, open_serial 		
	) KK
	INNER JOIN
	(
	select  
		store AS STORE_, 
		weekday AS WEEKDAY_, 
		close_serial, 
		count(*) CONTADOR_, 
		row_number() over (partition by store,weekday order by count(*) desc) close_serial_rank
	from [dbo].[CALENDARIO_LOJAS] (NOLOCK)
	where 
			date between '2021-05-01' and '2021-05-31'
			and store=11		
	group by store, weekday, close_serial
	) JJ 
	on KK.store = JJ.STORE_ and KK.weekday = JJ.WEEKDAY_
	where open_serial_rank=1 and close_serial_rank=1
) DD
ON HH.STORE = DD.STORE AND HH.WEEKDAY = DD.WEEKDAY
WHERE 
HH.date between '2021-06-01' and '2021-06-30'
and HH.store=11

O resultado final com os “novos” horários para Junho, aqui com amostragem até dia 18, é:

Estes resultados podem ser armazenados numa tabela temporária para posterior alteração na tabela [CALENDARIO_LOJAS].

Para o cálculo de todas as lojas apenas será necessário retirar o filtro da loja 11.

Espero que vos seja útil,

JG

BULK INSERT FILES WITH FIXED Width fields

Olá,
muitas vezes existem processos com necessidade de importação de dados de ficheiros texto para uma tabela SQL.
O BULK INSERT do SQL é uma boa ferramenta para esse efeito. A importação de um ficheiro para o SQL através de código com o BULK INSERT é algo básico; menos básico será a importação de diversos ficheiros, em diferentes pastas, para uma única tabela de SQL numa só importação.

Se a este processo se colocar a dificuldade de formatação dos ficheiros torna-se um processo moroso.

Com este artigo quero mostrar uma forma de importação de diversos ficheiros, em diferentes pastas, num único processo de SQL BULK INSERT, para uma única tabela. Além disso, mostro uma forma de importar os ficheiros de texto com largura de campos fixa, corrigindo problemas de formatação de colunas que poderiam surgir.

Assim, a minha estrutura de pastas e ficheiros é a seguinte:

importfolder

Foi criada uma estrutura com uma pasta mãe denominada “Folder” e no seu interior diversas pastas “01“, “02“, etc.
Dentro de cada uma destas pastas existem ficheiros de texto no seguinte formato:

importfolder2

Queremos juntar os dados dos diferentes ficheiros numa única tabela, mas devidamente separados por colunas de dados.

O script SQL passa por dois momentos:

  1. Cria tabela temporária para popular com o caminho e nome dos ficheiros
  2. Cria tabela temporária onde irá inserir os valores do ficheiros listados anteriormente , ou melhor, irá inserir os valores das colunas previamente criadas, através de um ficheiro de configuração XML, com as respectivas colunas de dados.

O primeiro passo será, então, criar a tabela temporária onde serão listados os ficheiros e respectivos caminhos de acesso aos mesmos. Além disso é criada um índice com intuito de melhor performance :

importfolder3

Agora vamos popular essa tabela:

importfolder4

O resultado final do script anterior leva aos seguintes resultados, para o meu caso específico de pastas e ficheiros:

importfolder5

O ficheiro FileFormat.xml deve ser eliminado desta tabela temporária:

importfolder6

De seguida, vamos criar uma tabela temporária para armazenamento dos resultados finais, isto é, o Merge de todos os registos de todos os ficheiros existentes nas diferentes pastas. A criação de três colunas será explicada mais à frente:

importfolder7_

O processo é idêntico a ter uma pilha (ou fila) de ficheiros que à medida que se insere um deles na tabela é eliminado dessa mesma pilha. Dessa forma, o número de ficheiro na pilha vai diminuindo até chegar a zero, altura que o ciclo termina.

O resultado final da tabela temporária é este:

importfolder8

Repare-se na estrutura de dados fixa que foi criada com três colunas. Foi apenas um exemplo, poderiam ser mais e de diversos tipos.

A formatação das colunas de valor fixo de largura e tipo de dados é configurada através do ficheiro FileFormat.XML listado acima.

O BULK INSERT utiliza o parâmetro de configuração de ficheiro externo (with FormatFile) permitindo a leitura em formato de diferentes campos fixos com consequente inserção no SQL.

O conteúdo do ficheiro de configuração FileFormat.XML é o seguinte:

importfolder9

Poderia ter optado por colocar mais colunas especificando seu tamanho e tipo, conforme fosse solicitado.

Por fim, todo o código poderá ser incluído numa Stored Procedure com o resultado final a ser incluído numa tabela física, procedendo posteriormente à eliminação da tabela temporária.

Espero que vos seja útil,

JG

Função SQL para Validar NIF

Olá,
esta semana necessitei de criar um script para validação do NIF, número de identificação fiscal, mais conhecido por número de contribuinte.

O código foi implementado em numa linguagem diferente mas achei interessante recriar o mesmo em TSQL.

Explicações mais profundas sobre esta matéria podem consultar por ex. aqui:
https://pt.m.wikipedia.org/wiki/N%C3%BAmero_de_identifica%C3%A7%C3%A3o_fiscal

Vou apenas resumir o algoritmo a implementar:

O NIF é constituído por nove dígitos, sendo os oito primeiros sequenciais e o último um dígito de controlo. Adicionalmente, o primeiro dígito do NIF não pode ser zero nem quatro.

Para ser calculado o digito de controlo:

Multiplicar 1º digito por 9, o 2.º dígito por 8, o 3.º dígito por 7, o 4.º dígito por 6, o 5.º dígito por 5, o 6.º dígito por 4, o 7.º dígito por 3 e o 8.º dígito por 2.

Seguidamente somar os resultados.

Calcular o resto da divisão do número por 11: se o resto for 0 (zero) ou 1 (um) o dígito de controlo será 0 (zero); se for outro qualquer algarismo X o dígito de controlo será o resultado da subtracção 11 – X.

Por fim, basta ver a igualdade do dígito de controlo com o último dígito do NIF. No caso de ser igual o NIF está correcto; no caso de ser diferente o NIF não é válido.

O código TSQL foi colocado como uma função. Esta é chamada com um NIF e o retorno será o próprio NIF se estiver válido ou vazio caso não o seja.
Denominei a função como [dbo].[up_check_nif] e codifica-se da seguinte forma:

/*
Função valida NIF,
2021 JG
*/

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

if OBJECT_ID('[dbo].[up_check_nif]') IS NOT NULL       drop function dbo.up_check_nif
GO

CREATE function [dbo].up_check_nif (@nif_input varchar(9))
RETURNS varchar(9)
AS
BEGIN

declare @j int = 9
declare @i int = 1
declare @total int = 0
declare @digit_control int 
declare @result varchar(9)
-- nif_pri_digito   = '1,2,3,5,6,7,8,9'

IF  LEN(@nif_input) = 9 AND @nif_input NOT LIKE '%[^0-9]%' AND LEFT(@nif_input,1) not in (0,4)      
BEGIN   

    -- SUM( primeiros 8 digitos * [9,8,7,6,5,4,3,2])
    WHILE @i < LEN(@nif_input)  
    BEGIN
         SET @total = @total + SUBSTRING(@nif_input,@i,1) * @j               
         SET @j = @j-1 
         SET @i = @i+1
    END             

    IF (@total % 11) = 0 OR (@total % 11) = 1 
         SET @digit_control = 0          
    ELSE            
         SET @digit_control = 11 - (@total % 11) 

    IF @digit_control = RIGHT(@nif_input,1)                                 
         SET @result = @nif_input   /* nif válido */        
    ELSE                    
         SET @result = ''          /* nif inválido */  
END
ELSE

        SET @result = ''           /* nif inválido */  

RETURN @result  

END

A função poderá ser chamada da seguinte forma:

select dbo.up_check_nif(‘123456789’) AS NIF_Validado

Espero que vos seja útil,

JG

Machine Learning: Bank Marketing Campaign Dataset

A equipa de marketing quer lançar uma nova campanha cujo objectivo é convencer os clientes a abrir depósitos a prazo.

Até agora a estratégia era ligar ao máximo número de pessoas, de forma indiscriminada, e tentar vender-lhe o produto. No entanto essa abordagem, para além de gastar mais recursos porque implica ter várias pessoas a ligar a todos os clientes, também é incómoda para alguns clientes que não gostam de ser incomodados com esse tipo de chamadas. Feitas as contas, chegou-se à conclusão que:

- Por cada cliente identificado como um bom candidado, e é alvo da campanha mas não adere ao depósito a prazo, o banco tem um custo de 500 euros.
- Por cada cliente que é identificado como mau candidado, e como tal não é alvo da campanha mas na verdade era um bom candidado e iria aderir, o banco tem um custo de 2000 euros.

Com base nesta informação, conseguem ajudar a equipa de marketing criando um modelo que seleccione os melhores candidatos para serem alvos da campanha, de forma a reduzir custos?

O dataset contêm informação sobre todos os clientes que foram alvo da campanha:

1 - age 
2 - job : type of job 
3 - marital : marital status
4 - education
5 - default: has credit in default? 
6 - housing: has housing loan? 
7 - loan: has personal loan? 
8 - pdays: number of days that passed by after the client was last contacted from a previous campaign 
9 - previous: number of contacts performed before this campaign and for this client 
10 - poutcome: outcome of the previous marketing campaign 
11 - emp.var.rate: employment variation rate - quarterly indicator 
12 - cons.price.idx: consumer price index - monthly indicator
13 - cons.conf.idx: consumer confidence index - monthly indicator 
14 - euribor3m: euribor 3 month rate - daily indicator 
15 - nr.employed: number of employees - quarterly indicator 

Output:

y: has the client subscribed a term deposit? 

Perguntas:

1. Quantas features estão disponíveis? Quantos clientes?
2. Quantos clientes têm no dataset que efectivamente aderiram ao depósito a prazo? E quantos não o fizeram?
3. Quais são as features mais relevantes para decidir se um cliente tem mais propensão para aderir ao depósito a prazo?
4. Qual o algoritmo que levou aos melhores resultados?
5. Qual/Quais as métricas de avaliação que usaram para comparar a performance dos vários modelos? Porquê?
6. Qual é o custo que o banco tem sem nenhum modelo?
7. Qual o custo que o banco passa a ter com o vosso modelo?   

Aceder ao projecto realizado em python.

https://github.com/JorgeGomes72/DataScience/blob/master/JGomes_project_Bank.ipynb

O projecto divide-se praticamente em cinco partes com todos os componentes desejados num processo de Data Science:
1) Análise, tratamento e transformação dos dados: remoção de variáveis, outliers, transformação de variáveis categóricas em numéricas, etc
2) Criação de uma Baseline da campanha imaginando que todos as pessoas acederiam à mesma
3) Procura dos melhores hiper-parâmetros dos diversos modelos a testar (é aqui que o processamento pode demorar horas!)
4) Criação dos modelos de machine learning, com os hiper-parâmetros recolhidos anteriormente
5) Análise dos resultados de todos os modelos testados e selecção do melhor, o que dará menor custo à campanha. Verificação das melhores features a usar no modelo.


No repositório existe também o dataset em formato csv.

Sintam-se à vontade para utilizar o código, espero que vos seja útil!

JG

Pesquisar stored procedures no SQL

Olá,
o artigo de hoje tem por objectivo facultar um script de pesquisa de stored procedures SQL através de qualquer palavra chave que nos recordemos.

Imaginemos que queremos saber se existe alguma “stored procedure” relacionada com a palavra “irregularidade”, mas que não sabemos de facto de existe ou qual seu nome – algo muito comum pelo menos comigo!

Para o caso a analisar, atentemos o seguinte código SQL:

select top 10 * from sys.syscomments where text like ‘%irregul%’

Este código permite pesquisar sp com uma palavra chave que imaginamos possa existir :

Do resultado obtido, a reter será o “id” e a coluna “text”, sendo respectivamente o identificador e o conteúdo do objecto SQL.

De seguida, poderemos escrever a seguinte linha de código:

select top 10 * from sys.all_objects where object_id=’178405579′

que nos mostra os objectos “stored procedures”, cuja identificação é ‘178405579’, ou seja, a mesma retirada do código anterior “id”.

Atente-se no resultado obtido onde se pode verificar que é uma “stored procedure”, em que data foi criada e alterada e respectivo nome.

Já agora, o top 10 neste caso não serve para nada, é apenas uma questão de não stressar o SQL no caso de existirem muitos registos.

Por fim, poderemos juntar ambos os códigos e cruzar as tabelas obtendo um script simples e mais intuitivo:

select * from sys.syscomments A
inner join sys.all_objects B
on A.id = B.object_id
where A.text like ‘%irregul%’

Repare-se que a palavra chave pode ser mesmo qualquer coisa que pensemos existir no processo, poderia ser uma variável como “@irreg”.

O resultado está na imagem seguinte:

Só falta mesmo construir o script com a definição da variável no topo de forma a alterar a mesma na pesquisa…mas como o código é demasiado pequeno não me dou ao trabalho, basta alterar a palavra chave no código acima.

Espero que vos seja útil,

JG

Assistência técnica TI, Desenvolvimento de aplicações, Desenvolvimento de sites e Segurança informática jorge_gomes98@hotmail.com Telm: 962988116