## knnGtk.R -- an example of using GtkNotebook, GtkEntry, GtkCheckButton, GtkCombo, and GtkButton ## ## by Artem Sokolov library( RGtk ) ############################################## ## Callbacks ############################################## nClasses.callback <- function( widget ) { nClasses <- as.integer( entry.nClasses$GetText() ) if( is.na( nClasses ) || nClasses < 1 || nClasses > 8 ) { cat( "Number of classes must be a positive integer between 1 and 8\n" ) return(0) } nBoxes <- length(class.coords) if( nBoxes < nClasses ) { ## Append more boxes for( i in (nBoxes+1):nClasses ) { entry.x <- gtkEntry(3); entry.x$SetUsize( 30, 20 ); entry.x$SetText( paste(i) ) entry.y <- gtkEntry(3); entry.y$SetUsize( 30, 20 ); entry.y$SetText( paste(round( 5*sin(i/2) )) ) class.coords[[i]] <<- gtkHBox( FALSE, 5 ) class.coords[[i]]$PackStart( gtkLabel( paste("X",i,sep="") ), FALSE ); class.coords[[i]]$PackStart( entry.x, FALSE ) class.coords[[i]]$PackStart( gtkLabel( paste("Y",i,sep="") ), FALSE ); class.coords[[i]]$PackStart( entry.y, FALSE ) first.page$PackStart( class.coords[[i]], FALSE ) } } if( nBoxes > nClasses ) { ## Remove extra boxes for( i in (nClasses+1):nBoxes ) first.page$Remove( class.coords[[i]] ) class.coords <<- class.coords[1:nClasses] } } train.callback <- function( widget ) { ## Retrieve class coordinates x.coords <- lapply( lapply( lapply( class.coords, gtkContainerGetChildren ), "[[", 2 ), gtkEntryGetText ) y.coords <- lapply( lapply( lapply( class.coords, gtkContainerGetChildren ), "[[", 4 ), gtkEntryGetText ) x.coords <- as.integer( unlist(x.coords) ) y.coords <- as.integer( unlist(y.coords) ) if( any( is.na(x.coords) ) || any( is.na(y.coords) ) ) { cat( "Coordinates must be integer values\n" ) return(0) } ## Generate data X <- cbind( rep( x.coords, each=10 ) + rnorm( length(x.coords)*10 ), rep( y.coords, each=10 ) + rnorm( length(y.coords)*10 ) ) Y <- rep( 1:length(x.coords), each=10 ) ## Generate grid bounds <- round( apply( X, 2, range ) ) bounds[1,] <- bounds[1,] - 1 bounds[2,] <- bounds[2,] + 1 myseq1 <- seq( bounds[1,1], bounds[2,1], length=20 ) myseq2 <- seq( bounds[1,2], bounds[2,2], length=20 ) my.grid.X <- expand.grid( myseq1, myseq2 ) my.grid.X <- as.matrix( my.grid.X ) ## Retrieve parameters dist.metr <- pmatch( dist.choice$GetEntry()$GetText(), c("Manhattan", "Euclidean" ) ) if( is.na(dist.metr) ) { cat( "Failed to resolve distance metric choice\n" ) return(0) } k <- as.integer( entry.nNeighbors$GetText() ) if( is.na(k) || k < 1 ) { cat( "The number of neighbors must be a positive integer\n" ) return(0) } ## Compute k nearest neighbors for each grid point my.grid.Y <- NULL for( i in 1:nrow(my.grid.X) ) { ## Replicate the current grid point mypoint <- matrix( my.grid.X[i,], nrow(X), 2, byrow=TRUE ) ## Compute distance mydist <- mypoint - X if( dist.metr == 1 ) mydist <- abs(mydist) else mydist <- mydist^2 mydist <- apply( mydist, 1, sum ) ## Find the closest neighbors myindex <- order( mydist )[1:k] ## Retrieve the corresponding labels and find the one that occurs most frequently mylabels <- Y[myindex] my.grid.Y <- c( my.grid.Y, which.max(hist(mylabels, breaks=1:9, right=FALSE,plot=FALSE)$counts) ) } plot( my.grid.X, col=my.grid.Y ) if( chk.contour$GetActive() == TRUE ) contour( x=myseq1, y=myseq2, z=matrix(my.grid.Y, 20, 20), levels=1:8+0.5, drawlabels=FALSE, add=TRUE ) points( X, col=Y, pch=19 ) } ############################################## ## GUI ############################################## ## Create main window main.window <- gtkWindow( show=FALSE ) main.window$SetTitle( "k nearest neighbors" ) ## Create the notebook the.notebook <- gtkNotebook() main.window$Add( the.notebook ) ## Create the first page first.page <- gtkVBox() the.notebook$AppendPage( first.page, gtkLabel("Data") ) ## Add content to the first page hbox.nClasses <- gtkHBox(FALSE, 5) hbox.nClasses$PackStart( gtkLabel("Number of classes:"), FALSE ) entry.nClasses <- gtkEntry(1) entry.nClasses$SetText( "3" ) entry.nClasses$SetUsize( 30, 20 ) entry.nClasses$AddCallback( "activate", nClasses.callback ) hbox.nClasses$PackStart( entry.nClasses, FALSE ) but.nClasses <- gtkButton( "Update" ) but.nClasses$AddCallback( "clicked", nClasses.callback ) hbox.nClasses$PackStart( but.nClasses, FALSE ) first.page$PackStart(hbox.nClasses, FALSE) class.coords <- list() # holds coordinates for each class nClasses.callback( entry.nClasses ) ## Create the second page second.page <- gtkVBox() the.notebook$AppendPage( second.page, gtkLabel("Parameters") ) ## Add content to the second page hbox.dist <- gtkHBox(FALSE, 10) hbox.dist$PackStart( gtkLabel( "Distance:" ), FALSE ) dist.choice <- gtkCombo() dist.choice$SetPopdownStrings( c("Euclidean", "Manhattan") ) dist.choice$DisableActivate() hbox.dist$PackStart( dist.choice, FALSE ) second.page$PackStart( hbox.dist, FALSE ) hbox.nNeighbors <- gtkHBox(FALSE, 50) hbox.nNeighbors$PackStart( gtkLabel( "Number of Neighbors:" ), FALSE ) entry.nNeighbors <- gtkEntry(1) entry.nNeighbors$SetText( "1" ) entry.nNeighbors$SetUsize( 30, 20 ) hbox.nNeighbors$PackStart( entry.nNeighbors, FALSE ) second.page$Add( hbox.nNeighbors ) chk.contour <- gtkCheckButton( "Draw Contour?" ) chk.contour$SetActive( TRUE ) second.page$Add( chk.contour ) but.train <- gtkButton( "Train" ) but.train$AddCallback( "clicked", train.callback ) second.page$Add( but.train ) ## Show the GUI main.window$Show()